@optionfactory/fml 9.0.0-rc31 → 9.0.0-rc33
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 +7 -0
- package/dist/fml.css +1 -1
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +683 -28
- package/dist/fml.iife.js.map +1 -1
- package/dist/fml.iife.min.js +1 -1
- package/dist/fml.iife.min.js.map +1 -1
- package/dist/ful.css +1 -1
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +68 -0
- package/dist/ful.iife.js +683 -28
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +683 -28
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +4 -0
- package/dist/web-types.json +9 -1
- package/package.json +1 -1
package/dist/fml.iife.js
CHANGED
|
@@ -10875,6 +10875,10 @@ var fml = (function (exports) {
|
|
|
10875
10875
|
* @typedef {{ data: any[], size: number }} TablePageResponse the rows of the
|
|
10876
10876
|
* requested page and the total number of rows across every page
|
|
10877
10877
|
*/
|
|
10878
|
+
/**
|
|
10879
|
+
* @typedef {{ count: Element, appending: Element, failure: Element, error: Element }} TableStatus the pieces
|
|
10880
|
+
* of the status bar `paging="infinite"` and `paging="virtual"` show in place of the pager
|
|
10881
|
+
*/
|
|
10878
10882
|
/**
|
|
10879
10883
|
* @typedef {{ load(pageRequest: TablePageRequest, sortRequest: TableSortRequest|null, filterRequest: Record<string, any>): Promise<TablePageResponse> }} TableRowLoader
|
|
10880
10884
|
*/
|
|
@@ -11347,6 +11351,21 @@ var fml = (function (exports) {
|
|
|
11347
11351
|
* for it. Without it the table shows an initial panel until `load`,
|
|
11348
11352
|
* `reload` or `resetWithFilter` is called.
|
|
11349
11353
|
*
|
|
11354
|
+
* `paging="infinite"`, read once at the upgrade, replaces the pager with a
|
|
11355
|
+
* status bar saying how many rows of how many are shown, with a reload
|
|
11356
|
+
* button. A load still replaces the rows with one page, scrolling the rows
|
|
11357
|
+
* back to the top; from then on, whenever a row of the last half page
|
|
11358
|
+
* comes into view, the next page is asked with the same size, sort and
|
|
11359
|
+
* filters and appended (see `load`). `reload`, a sort, a filter submit and a
|
|
11360
|
+
* size change start over from the first page.
|
|
11361
|
+
*
|
|
11362
|
+
* `paging="virtual"` shows the same status bar, saying which rows of how many
|
|
11363
|
+
* are in view, and keeps in the page only the page in view and one on either
|
|
11364
|
+
* side of it, the others standing in as empty space sized on the height of
|
|
11365
|
+
* the rows. The rows must scroll inside the table, as `ful-fill` makes them:
|
|
11366
|
+
* a table whose rows the page scrolls appends as `paging="infinite"` does,
|
|
11367
|
+
* and warns. See `load` and `reload`.
|
|
11368
|
+
*
|
|
11350
11369
|
* Inside the table, and not inside a `ful-table` nested in it, a
|
|
11351
11370
|
* `page:requested` loads that page with the current size,
|
|
11352
11371
|
* sort and filters; a `sort:requested` loads the current page with the new
|
|
@@ -11362,13 +11381,14 @@ var fml = (function (exports) {
|
|
|
11362
11381
|
*/
|
|
11363
11382
|
class Table extends ParsedElement {
|
|
11364
11383
|
/** Read once at the upgrade. */
|
|
11365
|
-
static attributes = ['loader', 'autoload:presence'];
|
|
11384
|
+
static attributes = ['loader', 'autoload:presence', 'paging'];
|
|
11366
11385
|
/** These stay live after the upgrade: see `pageSize`, `src` and `method`. */
|
|
11367
11386
|
static observed = ['page-size:number', 'src', 'method'];
|
|
11368
11387
|
static slots = true;
|
|
11369
11388
|
static config = {
|
|
11370
11389
|
searchIcon: 'search',
|
|
11371
11390
|
emptyIcon: 'inbox',
|
|
11391
|
+
reloadIcon: 'arrow-clockwise',
|
|
11372
11392
|
};
|
|
11373
11393
|
static template = `
|
|
11374
11394
|
<ful-form data-tpl-if="slots.filters">
|
|
@@ -11378,7 +11398,9 @@ var fml = (function (exports) {
|
|
|
11378
11398
|
<table>
|
|
11379
11399
|
<caption data-tpl-if="slots.caption">{{{{ slots.caption }}}}</caption>
|
|
11380
11400
|
<thead></thead>
|
|
11401
|
+
<tbody data-tpl-if="virtual" data-ref="before" aria-hidden="true"><tr><td data-tpl-colspan="schema.length"></td></tr></tbody>
|
|
11381
11402
|
<tbody></tbody>
|
|
11403
|
+
<tbody data-tpl-if="virtual" data-ref="after" aria-hidden="true"><tr><td data-tpl-colspan="schema.length"></td></tr></tbody>
|
|
11382
11404
|
<tbody data-ref="initial">
|
|
11383
11405
|
<tr>
|
|
11384
11406
|
<td data-tpl-colspan="schema.length">
|
|
@@ -11422,7 +11444,16 @@ var fml = (function (exports) {
|
|
|
11422
11444
|
</tfoot>
|
|
11423
11445
|
</table>
|
|
11424
11446
|
</ful-table-wrapper>
|
|
11425
|
-
<ful-pagination current="0" total="1"></ful-pagination>
|
|
11447
|
+
<ful-pagination data-tpl-if="!scrolled" current="0" total="1"></ful-pagination>
|
|
11448
|
+
<ful-table-status data-tpl-if="scrolled">
|
|
11449
|
+
<span data-ref="count"></span>
|
|
11450
|
+
<ful-spinner data-ref="appending" role="status" hidden><span class="ful-sr-only">{{ #l10n:t('spinner.loading') }}</span></ful-spinner>
|
|
11451
|
+
<div data-ref="append-failure" role="alert" hidden>
|
|
11452
|
+
<span>{{ #l10n:t('table.error') }} <span data-ref="append-error"></span></span>
|
|
11453
|
+
<button type="button" data-ref="retry">{{ #l10n:t('table.retry') }}</button>
|
|
11454
|
+
</div>
|
|
11455
|
+
<button type="button" data-ref="reload" data-tpl-aria-label="#l10n:t('pagination.reload')"><ful-icon data-tpl-name="config.reloadIcon" aria-hidden="true"></ful-icon></button>
|
|
11456
|
+
</ful-table-status>
|
|
11426
11457
|
`;
|
|
11427
11458
|
static templates = {
|
|
11428
11459
|
row: `
|
|
@@ -11440,6 +11471,31 @@ var fml = (function (exports) {
|
|
|
11440
11471
|
#feedback;
|
|
11441
11472
|
#paginator;
|
|
11442
11473
|
#sorters;
|
|
11474
|
+
#wrapper;
|
|
11475
|
+
/** @type {TableStatus|null} null under the pager */
|
|
11476
|
+
#status = null;
|
|
11477
|
+
/** @type {IntersectionObserver|null} */
|
|
11478
|
+
#endWatch = null;
|
|
11479
|
+
/** @type {Element[]} the rows whose coming into view appends the next page, none while no append is awaited */
|
|
11480
|
+
#watched = [];
|
|
11481
|
+
/** the page the next append asks for, the rows shown, the total the last answer reported and the rows it carried */
|
|
11482
|
+
#appended = { next: 1, shown: 0, total: 0, last: 0 };
|
|
11483
|
+
/** whether the table keeps a window of pages, `paging="virtual"` over rows that scroll inside it */
|
|
11484
|
+
#virtual = false;
|
|
11485
|
+
/** @type {{ before: HTMLElement, after: HTMLElement } | null} the cells standing in for the rows above and below the window */
|
|
11486
|
+
#spacers = null;
|
|
11487
|
+
/** @type {{ first: number, last: number, pages: Map<number, Element[]> } | null} the pages in the page, in order, null while no rows show */
|
|
11488
|
+
#window = null;
|
|
11489
|
+
/** the total number of rows the last answer reported */
|
|
11490
|
+
#total = 0;
|
|
11491
|
+
/** the height of a row the spacers are sized on, measured on the first rows a load shows; 0 until measured */
|
|
11492
|
+
#rowHeight = 0;
|
|
11493
|
+
/** @type {{ readonly stale: boolean } | null} the claim of the window shift in flight */
|
|
11494
|
+
#shifting = null;
|
|
11495
|
+
/** whether the last window shift failed: scrolling asks for nothing more until a retry or a load */
|
|
11496
|
+
#windowFailed = false;
|
|
11497
|
+
/** @type {ReturnType<typeof setTimeout>|undefined} */
|
|
11498
|
+
#jump = undefined;
|
|
11443
11499
|
/** @type {{ pageRequest: { page: number, size: number }, sortRequest: any, filterRequest: any }} */
|
|
11444
11500
|
#latestRequest = { pageRequest: { page: 0, size: 10 }, sortRequest: null, filterRequest: {} };
|
|
11445
11501
|
/** whether a load has been asked for, by autoload or by a caller */
|
|
@@ -11567,6 +11623,7 @@ var fml = (function (exports) {
|
|
|
11567
11623
|
}
|
|
11568
11624
|
this.#buildLoader();
|
|
11569
11625
|
this.#latestRequest = { ...this.#latestRequest, pageRequest: { ...this.#latestRequest.pageRequest, page: 0 } };
|
|
11626
|
+
this.#window = null;
|
|
11570
11627
|
return true;
|
|
11571
11628
|
}
|
|
11572
11629
|
/**
|
|
@@ -11577,14 +11634,24 @@ var fml = (function (exports) {
|
|
|
11577
11634
|
async render({ slots }) {
|
|
11578
11635
|
const template = this.template();
|
|
11579
11636
|
const schema = TableSchemaParser.parse(slots.schema, template);
|
|
11580
|
-
const
|
|
11637
|
+
const paging = this.declared('paging');
|
|
11638
|
+
const fragment = template
|
|
11639
|
+
.withOverlay({
|
|
11640
|
+
slots,
|
|
11641
|
+
schema,
|
|
11642
|
+
scrolled: paging === 'infinite' || paging === 'virtual',
|
|
11643
|
+
virtual: paging === 'virtual',
|
|
11644
|
+
})
|
|
11645
|
+
.render();
|
|
11581
11646
|
const tableWrapper = /** @type HTMLTableElement */ (Nodes.queryChildren(fragment, 'ful-table-wrapper'));
|
|
11582
11647
|
const table = /** @type HTMLTableElement */ (tableWrapper.querySelector('table'));
|
|
11583
11648
|
Attributes.forward('table-', this, table);
|
|
11584
11649
|
this.#buildLoader();
|
|
11585
11650
|
|
|
11651
|
+
this.#wrapper = tableWrapper;
|
|
11652
|
+
const status = Nodes.queryChildren(fragment, 'ful-table-status');
|
|
11586
11653
|
this.#schema = schema;
|
|
11587
|
-
this.#body = table.querySelector(':scope > tbody');
|
|
11654
|
+
this.#body = table.querySelector(':scope > tbody:not([data-ref])');
|
|
11588
11655
|
this.#loading = table.querySelector(':scope > tbody[data-ref=loading]');
|
|
11589
11656
|
this.#noAutoload = table.querySelector(':scope > tbody[data-ref=initial]');
|
|
11590
11657
|
this.#empty = table.querySelector(':scope > tbody[data-ref=empty]');
|
|
@@ -11594,6 +11661,63 @@ var fml = (function (exports) {
|
|
|
11594
11661
|
this.#feedback = table.querySelector(':scope > tbody[data-ref=feedback]');
|
|
11595
11662
|
this.#paginator = Nodes.queryChildren(fragment, 'ful-pagination');
|
|
11596
11663
|
this.replaceChildren(fragment);
|
|
11664
|
+
if (paging === 'virtual') {
|
|
11665
|
+
const overflow = getComputedStyle(tableWrapper).overflowY;
|
|
11666
|
+
this.#virtual = overflow === 'auto' || overflow === 'scroll';
|
|
11667
|
+
if (!this.#virtual) {
|
|
11668
|
+
console.warn(
|
|
11669
|
+
'ful-table: paging="virtual" needs rows that scroll inside the table, as ful-fill makes them; this one appends its pages as paging="infinite" does',
|
|
11670
|
+
);
|
|
11671
|
+
}
|
|
11672
|
+
}
|
|
11673
|
+
if (status) {
|
|
11674
|
+
const piece = (/** @type string */ ref) =>
|
|
11675
|
+
/** @type Element */ (status.querySelector(`[data-ref=${ref}]`));
|
|
11676
|
+
this.#status = {
|
|
11677
|
+
count: piece('count'),
|
|
11678
|
+
appending: piece('appending'),
|
|
11679
|
+
failure: piece('append-failure'),
|
|
11680
|
+
error: piece('append-error'),
|
|
11681
|
+
};
|
|
11682
|
+
piece('reload').addEventListener('click', () => this.reload());
|
|
11683
|
+
piece('retry').addEventListener('click', () => {
|
|
11684
|
+
if (!this.#virtual) {
|
|
11685
|
+
this.#appendNext();
|
|
11686
|
+
return;
|
|
11687
|
+
}
|
|
11688
|
+
this.#windowFailed = false;
|
|
11689
|
+
this.#reconcile(true);
|
|
11690
|
+
});
|
|
11691
|
+
}
|
|
11692
|
+
if (this.#virtual) {
|
|
11693
|
+
this.#spacers = {
|
|
11694
|
+
before: /** @type HTMLElement */ (table.querySelector(':scope > tbody[data-ref=before] td')),
|
|
11695
|
+
after: /** @type HTMLElement */ (table.querySelector(':scope > tbody[data-ref=after] td')),
|
|
11696
|
+
};
|
|
11697
|
+
// scroll fires once per frame at most, so it needs no throttle of its own
|
|
11698
|
+
tableWrapper.addEventListener(
|
|
11699
|
+
'scroll',
|
|
11700
|
+
() => {
|
|
11701
|
+
this.#countInView();
|
|
11702
|
+
this.#reconcile(false);
|
|
11703
|
+
},
|
|
11704
|
+
{ passive: true },
|
|
11705
|
+
);
|
|
11706
|
+
// a table that loaded while it was not laid out, in a tab not yet shown for one, has
|
|
11707
|
+
// measured nothing: with a first page shorter than its panel no scroll would ever come
|
|
11708
|
+
// to do it. Its box changing size is the moment it is laid out, or shows more rows.
|
|
11709
|
+
new ResizeObserver(() => {
|
|
11710
|
+
this.#countInView();
|
|
11711
|
+
this.#reconcile(false);
|
|
11712
|
+
}).observe(tableWrapper);
|
|
11713
|
+
} else if (status) {
|
|
11714
|
+
this.#endWatch = new IntersectionObserver((entries) => {
|
|
11715
|
+
// an entry queued before the watch moved on to another row, or stopped, asks for nothing
|
|
11716
|
+
if (entries.some((e) => e.isIntersecting && this.#watched.includes(e.target))) {
|
|
11717
|
+
this.#appendNext();
|
|
11718
|
+
}
|
|
11719
|
+
});
|
|
11720
|
+
}
|
|
11597
11721
|
const thead = /** @type HTMLTableSectionElement */ (this.querySelector('thead'));
|
|
11598
11722
|
schema.headersTemplate.renderTo(thead);
|
|
11599
11723
|
this.#sorters = thead.querySelectorAll('ful-sorter');
|
|
@@ -11650,6 +11774,14 @@ var fml = (function (exports) {
|
|
|
11650
11774
|
* `submit` listeners as the later ones, and the load its `submit:success`
|
|
11651
11775
|
* starts is the one awaited. A filter submit that is cancelled, fails or
|
|
11652
11776
|
* is dropped for one in flight loads nothing, and the promise resolves.
|
|
11777
|
+
*
|
|
11778
|
+
* Under `paging="virtual"`, once rows show, it asks again for the pages in
|
|
11779
|
+
* the page, with the same size, sort and filters, and shows them where
|
|
11780
|
+
* they are, the scroll position and the widths of the columns kept; the
|
|
11781
|
+
* table is busy and settles, fails and dispatches as a load does, one
|
|
11782
|
+
* `load:success` per page. When the answers report fewer rows than the
|
|
11783
|
+
* window starts at, it loads the first page instead. A `src` or `method`
|
|
11784
|
+
* write still starts over from the first page.
|
|
11653
11785
|
* @returns {Promise<void>} as `load`
|
|
11654
11786
|
*/
|
|
11655
11787
|
async reload() {
|
|
@@ -11659,6 +11791,9 @@ var fml = (function (exports) {
|
|
|
11659
11791
|
}
|
|
11660
11792
|
return;
|
|
11661
11793
|
}
|
|
11794
|
+
if (this.#window) {
|
|
11795
|
+
return await this.#reloadWindow();
|
|
11796
|
+
}
|
|
11662
11797
|
return await this.load(
|
|
11663
11798
|
this.#latestRequest.pageRequest,
|
|
11664
11799
|
this.#latestRequest.sortRequest,
|
|
@@ -11697,6 +11832,40 @@ var fml = (function (exports) {
|
|
|
11697
11832
|
* failure arrives it changes nothing, dispatches nothing and resolves;
|
|
11698
11833
|
* superseded while its rows render, it leaves them to the newer load and
|
|
11699
11834
|
* dispatches nothing.
|
|
11835
|
+
*
|
|
11836
|
+
* Under `paging="infinite"` the status bar takes the pager's place, and
|
|
11837
|
+
* once the load has dispatched `load:success` the next page is awaited:
|
|
11838
|
+
* when a row of the last half page comes into view, through
|
|
11839
|
+
* whatever scrolls it, the following page is asked for with the same
|
|
11840
|
+
* size, sort and filters and its rows are appended. An append runs
|
|
11841
|
+
* without `aria-busy`, a spinner in the status bar standing in, and
|
|
11842
|
+
* dispatches `load:success` or `load:failure` as a load does, its
|
|
11843
|
+
* `pageRequest` naming the appended page. A failed append keeps the
|
|
11844
|
+
* rows, shows the error in the status bar with a retry button and
|
|
11845
|
+
* rejects to nobody, nothing awaiting it. Appending stops once the rows
|
|
11846
|
+
* shown reach the total the last answer reported, or an answer carries
|
|
11847
|
+
* no rows. A load supersedes an append in flight; `reload` repeats this
|
|
11848
|
+
* load, not the pages appended to it.
|
|
11849
|
+
*
|
|
11850
|
+
* Under `paging="virtual"` the load shows its page as the window, scrolled
|
|
11851
|
+
* to where it sits among the total, and measures two things on its rows:
|
|
11852
|
+
* the height the empty space standing in for the other rows is sized on,
|
|
11853
|
+
* and the width of the columns, which then stay as they are, in
|
|
11854
|
+
* proportion, until the next load, so that the pages coming and going do
|
|
11855
|
+
* not move them. From then on the window follows the scroll: the page in
|
|
11856
|
+
* view and one on either side stay in the page and the others are dropped,
|
|
11857
|
+
* the page holding the focus kept while it is within two pages of them. A
|
|
11858
|
+
* page next to the window is asked for at once; a scroll landing farther
|
|
11859
|
+
* waits for the scroll to rest. Window pages are asked for without
|
|
11860
|
+
* `aria-busy`, a spinner in the status bar standing in, and dispatch
|
|
11861
|
+
* `load:success` or `load:failure` per page as appends do; a failure keeps
|
|
11862
|
+
* the rows and offers a retry. The row in view stays where it is as pages
|
|
11863
|
+
* come and go above it: the table measures it before and after and
|
|
11864
|
+
* corrects the scroll by whatever the browser's own scroll anchoring has
|
|
11865
|
+
* not kept, which is all of it where the browser anchors no table rows
|
|
11866
|
+
* (Firefox), has no scroll anchoring or the rows' container declares
|
|
11867
|
+
* `overflow-anchor: none`, and nothing where it has. The table carries
|
|
11868
|
+
* `aria-rowcount` and each row its `aria-rowindex`.
|
|
11700
11869
|
* @param {TablePageRequest} pageRequest
|
|
11701
11870
|
* @param {TableSortRequest|null} sortRequest
|
|
11702
11871
|
* @param {Record<string, any>} filterRequest
|
|
@@ -11707,6 +11876,13 @@ var fml = (function (exports) {
|
|
|
11707
11876
|
this.#loadRequested = true;
|
|
11708
11877
|
this.#requestedFilters = filterRequest;
|
|
11709
11878
|
const claim = this.#loads.take();
|
|
11879
|
+
this.#watched = [];
|
|
11880
|
+
this.#endWatch?.disconnect();
|
|
11881
|
+
clearTimeout(this.#jump);
|
|
11882
|
+
this.#shifting = null;
|
|
11883
|
+
this.#windowFailed = false;
|
|
11884
|
+
this.#status?.appending.setAttribute('hidden', '');
|
|
11885
|
+
this.#status?.failure.setAttribute('hidden', '');
|
|
11710
11886
|
this.#loading.toggleAttribute('hidden', this.#body.childElementCount > 0);
|
|
11711
11887
|
this.#feedback.setAttribute('hidden', '');
|
|
11712
11888
|
this.#noAutoload.setAttribute('hidden', '');
|
|
@@ -11736,6 +11912,9 @@ var fml = (function (exports) {
|
|
|
11736
11912
|
if (claim.stale) {
|
|
11737
11913
|
return;
|
|
11738
11914
|
}
|
|
11915
|
+
if (this.#virtual) {
|
|
11916
|
+
this.#settleWindow();
|
|
11917
|
+
}
|
|
11739
11918
|
this.removeAttribute('aria-busy');
|
|
11740
11919
|
this.dispatchEvent(
|
|
11741
11920
|
new CustomEvent('load:success', {
|
|
@@ -11744,24 +11923,15 @@ var fml = (function (exports) {
|
|
|
11744
11923
|
detail: { pageRequest, sortRequest, filterRequest, response: pageResponse },
|
|
11745
11924
|
}),
|
|
11746
11925
|
);
|
|
11926
|
+
this.#watchEnd(pageRequest.size);
|
|
11927
|
+
this.#reconcile(false);
|
|
11747
11928
|
return;
|
|
11748
11929
|
}
|
|
11749
11930
|
} catch (/** @type any */ error) {
|
|
11750
11931
|
if (claim.stale) {
|
|
11751
11932
|
return;
|
|
11752
11933
|
}
|
|
11753
|
-
this.#
|
|
11754
|
-
this.#body.replaceChildren();
|
|
11755
|
-
this.#feedback.removeAttribute('hidden');
|
|
11756
|
-
this.#feedback.querySelector('[data-ref=feedback-error]').textContent = Localization.of().failure(error);
|
|
11757
|
-
this.removeAttribute('aria-busy');
|
|
11758
|
-
this.dispatchEvent(
|
|
11759
|
-
new CustomEvent('load:failure', {
|
|
11760
|
-
bubbles: true,
|
|
11761
|
-
cancelable: false,
|
|
11762
|
-
detail: { pageRequest, sortRequest, filterRequest, exception: error },
|
|
11763
|
-
}),
|
|
11764
|
-
);
|
|
11934
|
+
this.#fail(error, { pageRequest, sortRequest, filterRequest });
|
|
11765
11935
|
throw error;
|
|
11766
11936
|
}
|
|
11767
11937
|
return await this.load({ page: lastPage, size: pageRequest.size }, sortRequest, filterRequest);
|
|
@@ -11792,19 +11962,471 @@ var fml = (function (exports) {
|
|
|
11792
11962
|
filterRequest,
|
|
11793
11963
|
);
|
|
11794
11964
|
}
|
|
11795
|
-
|
|
11796
|
-
|
|
11965
|
+
/**
|
|
11966
|
+
* Watches the last half page of rows, so that any of them coming into
|
|
11967
|
+
* view appends the next page, unless the rows shown already reach the
|
|
11968
|
+
* total or the last answer carried none. A single row would be missed by
|
|
11969
|
+
* a scroll jumping past it, to the end for instance.
|
|
11970
|
+
* @param {number} size the rows a page asks for
|
|
11971
|
+
*/
|
|
11972
|
+
#watchEnd(size) {
|
|
11973
|
+
const { shown, total, last } = this.#appended;
|
|
11974
|
+
if (!this.#endWatch || !(shown < total) || last === 0) {
|
|
11975
|
+
return;
|
|
11976
|
+
}
|
|
11977
|
+
const rows = [...this.#body.children];
|
|
11978
|
+
this.#watched = rows.slice(Math.max(0, rows.length - Math.ceil(size / 2)));
|
|
11979
|
+
for (const row of this.#watched) {
|
|
11980
|
+
this.#endWatch.observe(row);
|
|
11981
|
+
}
|
|
11982
|
+
}
|
|
11983
|
+
async #appendNext() {
|
|
11984
|
+
const status = /** @type TableStatus */ (this.#status);
|
|
11985
|
+
this.#watched = [];
|
|
11986
|
+
this.#endWatch?.disconnect();
|
|
11987
|
+
const { sortRequest, filterRequest } = this.#latestRequest;
|
|
11988
|
+
const pageRequest = { page: this.#appended.next, size: this.#latestRequest.pageRequest.size };
|
|
11989
|
+
const claim = this.#loads.take();
|
|
11990
|
+
status.failure.setAttribute('hidden', '');
|
|
11991
|
+
status.appending.removeAttribute('hidden');
|
|
11992
|
+
try {
|
|
11993
|
+
const pageResponse = await this.#loader.load(pageRequest, sortRequest, filterRequest);
|
|
11994
|
+
if (claim.stale) {
|
|
11995
|
+
return;
|
|
11996
|
+
}
|
|
11997
|
+
this.#body.append(
|
|
11998
|
+
this.template('row')
|
|
11999
|
+
.withOverlay({ schema: this.#schema, pageRequest, filterRequest, pageResponse })
|
|
12000
|
+
.render(),
|
|
12001
|
+
);
|
|
12002
|
+
this.#appended = {
|
|
12003
|
+
next: pageRequest.page + 1,
|
|
12004
|
+
shown: this.#appended.shown + pageResponse.data.length,
|
|
12005
|
+
total: pageResponse.size,
|
|
12006
|
+
last: pageResponse.data.length,
|
|
12007
|
+
};
|
|
12008
|
+
status.count.textContent = Localization.of().t('table.loaded', {
|
|
12009
|
+
loaded: this.#appended.shown,
|
|
12010
|
+
total: this.#appended.total,
|
|
12011
|
+
});
|
|
12012
|
+
await Rendering.waitForChildren(this.#body).catch(() => undefined);
|
|
12013
|
+
if (claim.stale) {
|
|
12014
|
+
return;
|
|
12015
|
+
}
|
|
12016
|
+
status.appending.setAttribute('hidden', '');
|
|
12017
|
+
this.dispatchEvent(
|
|
12018
|
+
new CustomEvent('load:success', {
|
|
12019
|
+
bubbles: true,
|
|
12020
|
+
cancelable: false,
|
|
12021
|
+
detail: { pageRequest, sortRequest, filterRequest, response: pageResponse },
|
|
12022
|
+
}),
|
|
12023
|
+
);
|
|
12024
|
+
this.#watchEnd(pageRequest.size);
|
|
12025
|
+
} catch (/** @type any */ error) {
|
|
12026
|
+
if (claim.stale) {
|
|
12027
|
+
return;
|
|
12028
|
+
}
|
|
12029
|
+
status.appending.setAttribute('hidden', '');
|
|
12030
|
+
status.error.textContent = Localization.of().failure(error);
|
|
12031
|
+
status.failure.removeAttribute('hidden');
|
|
12032
|
+
this.dispatchEvent(
|
|
12033
|
+
new CustomEvent('load:failure', {
|
|
12034
|
+
bubbles: true,
|
|
12035
|
+
cancelable: false,
|
|
12036
|
+
detail: { pageRequest, sortRequest, filterRequest, exception: error },
|
|
12037
|
+
}),
|
|
12038
|
+
);
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
/**
|
|
12042
|
+
* Renders the rows of a page, numbering them with `aria-rowindex` under
|
|
12043
|
+
* `paging="virtual"`, the header row being the first.
|
|
12044
|
+
* @param {TablePageRequest} pageRequest
|
|
12045
|
+
* @param {Record<string, any>} filterRequest
|
|
12046
|
+
* @param {TablePageResponse} pageResponse
|
|
12047
|
+
* @returns {Element[]}
|
|
12048
|
+
*/
|
|
12049
|
+
#pageRows(pageRequest, filterRequest, pageResponse) {
|
|
12050
|
+
const rows = [
|
|
12051
|
+
...this.template('row')
|
|
12052
|
+
.withOverlay({ schema: this.#schema, pageRequest, filterRequest, pageResponse })
|
|
12053
|
+
.render().children,
|
|
12054
|
+
];
|
|
12055
|
+
if (this.#virtual) {
|
|
12056
|
+
rows.forEach((row, i) => {
|
|
12057
|
+
row.setAttribute('aria-rowindex', String(pageRequest.page * pageRequest.size + i + 2));
|
|
12058
|
+
});
|
|
12059
|
+
}
|
|
12060
|
+
return rows;
|
|
12061
|
+
}
|
|
12062
|
+
/**
|
|
12063
|
+
* Shows a failed load: the rows go, the error panel shows and
|
|
12064
|
+
* `load:failure` is dispatched.
|
|
12065
|
+
* @param {any} error
|
|
12066
|
+
* @param {{ pageRequest: TablePageRequest, sortRequest: TableSortRequest|null, filterRequest: Record<string, any> }} request
|
|
12067
|
+
*/
|
|
12068
|
+
#fail(error, request) {
|
|
11797
12069
|
this.#loading.setAttribute('hidden', '');
|
|
11798
|
-
this.#body.replaceChildren(
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
12070
|
+
this.#body.replaceChildren();
|
|
12071
|
+
this.#window = null;
|
|
12072
|
+
this.#fitSpacers();
|
|
12073
|
+
this.#status?.count.replaceChildren();
|
|
12074
|
+
this.#feedback.removeAttribute('hidden');
|
|
12075
|
+
this.#feedback.querySelector('[data-ref=feedback-error]').textContent = Localization.of().failure(error);
|
|
12076
|
+
this.removeAttribute('aria-busy');
|
|
12077
|
+
this.dispatchEvent(
|
|
12078
|
+
new CustomEvent('load:failure', {
|
|
12079
|
+
bubbles: true,
|
|
12080
|
+
cancelable: false,
|
|
12081
|
+
detail: { ...request, exception: error },
|
|
12082
|
+
}),
|
|
11807
12083
|
);
|
|
12084
|
+
}
|
|
12085
|
+
/** Counts the rows of a load just shown and moves the window where its page sits. */
|
|
12086
|
+
#settleWindow() {
|
|
12087
|
+
const table = /** @type HTMLTableElement */ (this.#body.parentElement);
|
|
12088
|
+
table.setAttribute('aria-rowcount', String(this.#total + 1));
|
|
12089
|
+
table.tHead?.rows[0]?.setAttribute('aria-rowindex', '1');
|
|
12090
|
+
const status = /** @type TableStatus */ (this.#status);
|
|
12091
|
+
const window = this.#window;
|
|
12092
|
+
if (!window) {
|
|
12093
|
+
this.#fitSpacers();
|
|
12094
|
+
status.count.textContent = Localization.of().t('table.loaded', { loaded: 0, total: this.#total });
|
|
12095
|
+
return;
|
|
12096
|
+
}
|
|
12097
|
+
const size = this.#latestRequest.pageRequest.size;
|
|
12098
|
+
const from = window.first * size;
|
|
12099
|
+
status.count.textContent = Localization.of().t('table.range', {
|
|
12100
|
+
from: from + 1,
|
|
12101
|
+
to: from + /** @type Element[] */ (window.pages.get(window.first)).length,
|
|
12102
|
+
total: this.#total,
|
|
12103
|
+
});
|
|
12104
|
+
if (this.#measureRows()) {
|
|
12105
|
+
this.#wrapper.scrollTop = from * this.#rowHeight;
|
|
12106
|
+
}
|
|
12107
|
+
}
|
|
12108
|
+
/**
|
|
12109
|
+
* Measures the height of a row and locks the widths of the columns, once
|
|
12110
|
+
* per load, on the rows it shows, then sizes the spacers. A table that is not laid out, inside a
|
|
12111
|
+
* closed disclosure for instance, measures nothing and is measured when
|
|
12112
|
+
* it is laid out, or when its rows next scroll.
|
|
12113
|
+
* @returns {boolean} whether the rows are measured
|
|
12114
|
+
*/
|
|
12115
|
+
#measureRows() {
|
|
12116
|
+
if (this.#rowHeight > 0) {
|
|
12117
|
+
return true;
|
|
12118
|
+
}
|
|
12119
|
+
const count = this.#body.childElementCount;
|
|
12120
|
+
const height = this.#body.getBoundingClientRect().height;
|
|
12121
|
+
if (count === 0 || height === 0) {
|
|
12122
|
+
return false;
|
|
12123
|
+
}
|
|
12124
|
+
this.#rowHeight = height / count;
|
|
12125
|
+
this.#lockColumns();
|
|
12126
|
+
this.#fitSpacers();
|
|
12127
|
+
return true;
|
|
12128
|
+
}
|
|
12129
|
+
/**
|
|
12130
|
+
* Fixes the widths of the columns, in proportion to the table, at those
|
|
12131
|
+
* the rows in the page give them.
|
|
12132
|
+
*/
|
|
12133
|
+
#lockColumns() {
|
|
12134
|
+
const table = /** @type HTMLTableElement */ (this.#body.parentElement);
|
|
12135
|
+
const widths = [...(table.tHead?.rows[0]?.cells ?? [])].map((cell) => cell.getBoundingClientRect().width);
|
|
12136
|
+
const sum = widths.reduce((a, b) => a + b, 0);
|
|
12137
|
+
const columns = document.createElement('colgroup');
|
|
12138
|
+
columns.setAttribute('data-ref', 'widths');
|
|
12139
|
+
for (const width of widths) {
|
|
12140
|
+
const column = document.createElement('col');
|
|
12141
|
+
column.style.width = `${(width / sum) * 100}%`;
|
|
12142
|
+
columns.append(column);
|
|
12143
|
+
}
|
|
12144
|
+
table.insertBefore(columns, table.tHead);
|
|
12145
|
+
}
|
|
12146
|
+
#unlockColumns() {
|
|
12147
|
+
this.#body.parentElement?.querySelector(':scope > colgroup[data-ref=widths]')?.remove();
|
|
12148
|
+
}
|
|
12149
|
+
/** Sizes the space standing in for the rows above and below the window. */
|
|
12150
|
+
#fitSpacers() {
|
|
12151
|
+
if (!this.#spacers) {
|
|
12152
|
+
return;
|
|
12153
|
+
}
|
|
12154
|
+
const window = this.#window;
|
|
12155
|
+
const size = this.#latestRequest.pageRequest.size;
|
|
12156
|
+
const above = window ? window.first * size : 0;
|
|
12157
|
+
const below = window ? Math.max(0, this.#total - (window.last + 1) * size) : 0;
|
|
12158
|
+
this.#spacers.before.style.height = `${above * this.#rowHeight}px`;
|
|
12159
|
+
this.#spacers.after.style.height = `${below * this.#rowHeight}px`;
|
|
12160
|
+
}
|
|
12161
|
+
/** Says in the status bar which rows are in view; nothing changes while none is. */
|
|
12162
|
+
#countInView() {
|
|
12163
|
+
const status = /** @type TableStatus */ (this.#status);
|
|
12164
|
+
const size = this.#latestRequest.pageRequest.size;
|
|
12165
|
+
const view = this.#wrapper.getBoundingClientRect();
|
|
12166
|
+
const top = view.top + (this.#body.parentElement?.querySelector(':scope > thead')?.getBoundingClientRect().height ?? 0);
|
|
12167
|
+
let from = 0;
|
|
12168
|
+
let to = 0;
|
|
12169
|
+
for (const [page, rows] of this.#window?.pages ?? []) {
|
|
12170
|
+
rows.forEach((row, i) => {
|
|
12171
|
+
const rect = row.getBoundingClientRect();
|
|
12172
|
+
if (rect.bottom > top && rect.top < view.bottom) {
|
|
12173
|
+
from ||= page * size + i + 1;
|
|
12174
|
+
to = page * size + i + 1;
|
|
12175
|
+
}
|
|
12176
|
+
});
|
|
12177
|
+
}
|
|
12178
|
+
if (from > 0) {
|
|
12179
|
+
status.count.textContent = Localization.of().t('table.range', { from, to, total: this.#total });
|
|
12180
|
+
}
|
|
12181
|
+
}
|
|
12182
|
+
/**
|
|
12183
|
+
* The page at the middle of the view: found among the rows in the page,
|
|
12184
|
+
* or estimated on the height of a row when the middle falls in the space
|
|
12185
|
+
* standing in for others.
|
|
12186
|
+
* @param {{ first: number, last: number, pages: Map<number, Element[]> }} window
|
|
12187
|
+
* @returns {number}
|
|
12188
|
+
*/
|
|
12189
|
+
#pageInView(window) {
|
|
12190
|
+
const pageHeight = this.#latestRequest.pageRequest.size * this.#rowHeight;
|
|
12191
|
+
const view = this.#wrapper.getBoundingClientRect();
|
|
12192
|
+
const middle = view.top + view.height / 2;
|
|
12193
|
+
const rows = this.#body.getBoundingClientRect();
|
|
12194
|
+
if (middle < rows.top) {
|
|
12195
|
+
return Math.max(0, window.first - Math.ceil((rows.top - middle) / pageHeight));
|
|
12196
|
+
}
|
|
12197
|
+
if (middle >= rows.bottom) {
|
|
12198
|
+
return window.last + 1 + Math.floor((middle - rows.bottom) / pageHeight);
|
|
12199
|
+
}
|
|
12200
|
+
let found = window.first;
|
|
12201
|
+
for (const [page, pageRows] of window.pages) {
|
|
12202
|
+
if (pageRows.length > 0 && pageRows[0].getBoundingClientRect().top <= middle) {
|
|
12203
|
+
found = page;
|
|
12204
|
+
}
|
|
12205
|
+
}
|
|
12206
|
+
return found;
|
|
12207
|
+
}
|
|
12208
|
+
/**
|
|
12209
|
+
* Moves the window to the page in view and its neighbours. A window next
|
|
12210
|
+
* to the current one moves at once; one farther away waits for the scroll
|
|
12211
|
+
* to rest, unless `settled` says it has.
|
|
12212
|
+
* @param {boolean} settled
|
|
12213
|
+
*/
|
|
12214
|
+
#reconcile(settled) {
|
|
12215
|
+
const window = this.#window;
|
|
12216
|
+
const pageCount = Math.ceil(this.#total / this.#latestRequest.pageRequest.size);
|
|
12217
|
+
if (
|
|
12218
|
+
!window ||
|
|
12219
|
+
this.#shifting ||
|
|
12220
|
+
this.#windowFailed ||
|
|
12221
|
+
this.hasAttribute('aria-busy') ||
|
|
12222
|
+
!this.#wrapper.isConnected ||
|
|
12223
|
+
!(pageCount > 0) ||
|
|
12224
|
+
!this.#measureRows()
|
|
12225
|
+
) {
|
|
12226
|
+
return;
|
|
12227
|
+
}
|
|
12228
|
+
const target = Math.min(pageCount - 1, this.#pageInView(window));
|
|
12229
|
+
let first = Math.max(0, target - 1);
|
|
12230
|
+
let last = Math.min(pageCount - 1, target + 1);
|
|
12231
|
+
const focused = this.#body.contains(document.activeElement)
|
|
12232
|
+
? /** @type Element */ (document.activeElement).closest('tr')
|
|
12233
|
+
: null;
|
|
12234
|
+
const focusedPage = [...window.pages].find(([, rows]) => focused && rows.includes(focused))?.[0];
|
|
12235
|
+
if (focusedPage !== undefined && Math.max(last, focusedPage) - Math.min(first, focusedPage) <= 4) {
|
|
12236
|
+
first = Math.min(first, focusedPage);
|
|
12237
|
+
last = Math.max(last, focusedPage);
|
|
12238
|
+
}
|
|
12239
|
+
if (first === window.first && last === window.last) {
|
|
12240
|
+
return;
|
|
12241
|
+
}
|
|
12242
|
+
clearTimeout(this.#jump);
|
|
12243
|
+
if (!settled && (last < window.first - 1 || first > window.last + 1)) {
|
|
12244
|
+
this.#jump = setTimeout(() => this.#reconcile(true), 150);
|
|
12245
|
+
return;
|
|
12246
|
+
}
|
|
12247
|
+
this.#shiftWindow(window, first, last);
|
|
12248
|
+
}
|
|
12249
|
+
/**
|
|
12250
|
+
* Asks for the pages of the new window that are not in the page, drops
|
|
12251
|
+
* the ones outside it and keeps the row in view where it is.
|
|
12252
|
+
* @param {{ first: number, last: number, pages: Map<number, Element[]> }} window
|
|
12253
|
+
* @param {number} first
|
|
12254
|
+
* @param {number} last
|
|
12255
|
+
*/
|
|
12256
|
+
async #shiftWindow(window, first, last) {
|
|
12257
|
+
const status = /** @type TableStatus */ (this.#status);
|
|
12258
|
+
const claim = this.#loads.hold();
|
|
12259
|
+
this.#shifting = claim;
|
|
12260
|
+
const { sortRequest, filterRequest } = this.#latestRequest;
|
|
12261
|
+
const size = this.#latestRequest.pageRequest.size;
|
|
12262
|
+
const wanted = Array.from({ length: last - first + 1 }, (_, i) => first + i);
|
|
12263
|
+
const missing = wanted.filter((page) => !window.pages.has(page));
|
|
12264
|
+
status.failure.setAttribute('hidden', '');
|
|
12265
|
+
status.appending.removeAttribute('hidden');
|
|
12266
|
+
try {
|
|
12267
|
+
const answers = await Promise.all(
|
|
12268
|
+
missing.map(async (page) => {
|
|
12269
|
+
const pageRequest = { page, size };
|
|
12270
|
+
return { pageRequest, response: await this.#loader.load(pageRequest, sortRequest, filterRequest) };
|
|
12271
|
+
}),
|
|
12272
|
+
);
|
|
12273
|
+
if (claim.stale) {
|
|
12274
|
+
return;
|
|
12275
|
+
}
|
|
12276
|
+
const kept = wanted.filter((page) => window.pages.has(page));
|
|
12277
|
+
const top = this.#wrapper.getBoundingClientRect().top;
|
|
12278
|
+
const anchor = kept
|
|
12279
|
+
.flatMap((page) => window.pages.get(page) ?? [])
|
|
12280
|
+
.find((row) => row.getBoundingClientRect().bottom > top);
|
|
12281
|
+
const anchorTop = anchor?.getBoundingClientRect().top ?? 0;
|
|
12282
|
+
for (const [page, rows] of window.pages) {
|
|
12283
|
+
if (page < first || page > last) {
|
|
12284
|
+
for (const row of rows) {
|
|
12285
|
+
row.remove();
|
|
12286
|
+
}
|
|
12287
|
+
}
|
|
12288
|
+
}
|
|
12289
|
+
/** @type {Map<number, Element[]>} */
|
|
12290
|
+
const pages = new Map();
|
|
12291
|
+
for (const page of wanted) {
|
|
12292
|
+
const answer = answers.find((a) => a.pageRequest.page === page);
|
|
12293
|
+
pages.set(
|
|
12294
|
+
page,
|
|
12295
|
+
window.pages.get(page) ??
|
|
12296
|
+
(answer ? this.#pageRows(answer.pageRequest, filterRequest, answer.response) : []),
|
|
12297
|
+
);
|
|
12298
|
+
}
|
|
12299
|
+
if (kept.length === 0) {
|
|
12300
|
+
this.#body.replaceChildren(...[...pages.values()].flat());
|
|
12301
|
+
} else {
|
|
12302
|
+
this.#body.prepend(...wanted.filter((page) => page < kept[0]).flatMap((page) => pages.get(page) ?? []));
|
|
12303
|
+
this.#body.append(
|
|
12304
|
+
...wanted.filter((page) => page > /** @type number */ (kept.at(-1))).flatMap((page) => pages.get(page) ?? []),
|
|
12305
|
+
);
|
|
12306
|
+
}
|
|
12307
|
+
this.#window = { first, last, pages };
|
|
12308
|
+
this.#total = answers.at(-1)?.response.size ?? this.#total;
|
|
12309
|
+
this.#body.parentElement?.setAttribute('aria-rowcount', String(this.#total + 1));
|
|
12310
|
+
this.#fitSpacers();
|
|
12311
|
+
if (anchor) {
|
|
12312
|
+
this.#wrapper.scrollTop += anchor.getBoundingClientRect().top - anchorTop;
|
|
12313
|
+
}
|
|
12314
|
+
await Rendering.waitForChildren(this.#body).catch(() => undefined);
|
|
12315
|
+
if (claim.stale) {
|
|
12316
|
+
return;
|
|
12317
|
+
}
|
|
12318
|
+
if (anchor) {
|
|
12319
|
+
// the elements in the new rows have rendered since, which can change their height
|
|
12320
|
+
this.#wrapper.scrollTop += anchor.getBoundingClientRect().top - anchorTop;
|
|
12321
|
+
}
|
|
12322
|
+
status.appending.setAttribute('hidden', '');
|
|
12323
|
+
this.#countInView();
|
|
12324
|
+
for (const { pageRequest, response } of answers) {
|
|
12325
|
+
this.dispatchEvent(
|
|
12326
|
+
new CustomEvent('load:success', {
|
|
12327
|
+
bubbles: true,
|
|
12328
|
+
cancelable: false,
|
|
12329
|
+
detail: { pageRequest, sortRequest, filterRequest, response },
|
|
12330
|
+
}),
|
|
12331
|
+
);
|
|
12332
|
+
}
|
|
12333
|
+
} catch (/** @type any */ error) {
|
|
12334
|
+
if (claim.stale) {
|
|
12335
|
+
return;
|
|
12336
|
+
}
|
|
12337
|
+
this.#windowFailed = true;
|
|
12338
|
+
status.appending.setAttribute('hidden', '');
|
|
12339
|
+
status.error.textContent = Localization.of().failure(error);
|
|
12340
|
+
status.failure.removeAttribute('hidden');
|
|
12341
|
+
this.dispatchEvent(
|
|
12342
|
+
new CustomEvent('load:failure', {
|
|
12343
|
+
bubbles: true,
|
|
12344
|
+
cancelable: false,
|
|
12345
|
+
detail: { pageRequest: { page: missing[0], size }, sortRequest, filterRequest, exception: error },
|
|
12346
|
+
}),
|
|
12347
|
+
);
|
|
12348
|
+
return;
|
|
12349
|
+
} finally {
|
|
12350
|
+
if (this.#shifting === claim) {
|
|
12351
|
+
this.#shifting = null;
|
|
12352
|
+
}
|
|
12353
|
+
}
|
|
12354
|
+
this.#reconcile(false);
|
|
12355
|
+
}
|
|
12356
|
+
/**
|
|
12357
|
+
* Asks again for the pages of the window and shows them where they are.
|
|
12358
|
+
* @returns {Promise<void>}
|
|
12359
|
+
*/
|
|
12360
|
+
async #reloadWindow() {
|
|
12361
|
+
const window = /** @type {{ first: number, last: number, pages: Map<number, Element[]> }} */ (this.#window);
|
|
12362
|
+
const status = /** @type TableStatus */ (this.#status);
|
|
12363
|
+
const { sortRequest, filterRequest } = this.#latestRequest;
|
|
12364
|
+
const size = this.#latestRequest.pageRequest.size;
|
|
12365
|
+
this.#requestedFilters = filterRequest;
|
|
12366
|
+
const claim = this.#loads.take();
|
|
12367
|
+
clearTimeout(this.#jump);
|
|
12368
|
+
this.#shifting = null;
|
|
12369
|
+
this.#windowFailed = false;
|
|
12370
|
+
status.appending.setAttribute('hidden', '');
|
|
12371
|
+
status.failure.setAttribute('hidden', '');
|
|
12372
|
+
this.setAttribute('aria-busy', 'true');
|
|
12373
|
+
const wanted = Array.from({ length: window.last - window.first + 1 }, (_, i) => window.first + i);
|
|
12374
|
+
try {
|
|
12375
|
+
const answers = await Promise.all(
|
|
12376
|
+
wanted.map(async (page) => {
|
|
12377
|
+
const pageRequest = { page, size };
|
|
12378
|
+
return { pageRequest, response: await this.#loader.load(pageRequest, sortRequest, filterRequest) };
|
|
12379
|
+
}),
|
|
12380
|
+
);
|
|
12381
|
+
if (claim.stale) {
|
|
12382
|
+
return;
|
|
12383
|
+
}
|
|
12384
|
+
const total = /** @type {{ response: TablePageResponse }} */ (answers.at(-1)).response.size;
|
|
12385
|
+
const pageCount = Math.ceil(total / size);
|
|
12386
|
+
if (!(window.first < pageCount)) {
|
|
12387
|
+
return await this.load({ page: 0, size }, sortRequest, filterRequest);
|
|
12388
|
+
}
|
|
12389
|
+
const shown = answers.filter((a) => a.pageRequest.page < pageCount);
|
|
12390
|
+
const pages = new Map(
|
|
12391
|
+
shown.map((a) => [a.pageRequest.page, this.#pageRows(a.pageRequest, filterRequest, a.response)]),
|
|
12392
|
+
);
|
|
12393
|
+
this.#body.replaceChildren(...[...pages.values()].flat());
|
|
12394
|
+
this.#window = { first: window.first, last: Math.min(window.last, pageCount - 1), pages };
|
|
12395
|
+
this.#total = total;
|
|
12396
|
+
this.#body.parentElement?.setAttribute('aria-rowcount', String(total + 1));
|
|
12397
|
+
this.#fitSpacers();
|
|
12398
|
+
await Rendering.waitForChildren(this.#body).catch(() => undefined);
|
|
12399
|
+
if (claim.stale) {
|
|
12400
|
+
return;
|
|
12401
|
+
}
|
|
12402
|
+
this.removeAttribute('aria-busy');
|
|
12403
|
+
this.#countInView();
|
|
12404
|
+
for (const { pageRequest, response } of shown) {
|
|
12405
|
+
this.dispatchEvent(
|
|
12406
|
+
new CustomEvent('load:success', {
|
|
12407
|
+
bubbles: true,
|
|
12408
|
+
cancelable: false,
|
|
12409
|
+
detail: { pageRequest, sortRequest, filterRequest, response },
|
|
12410
|
+
}),
|
|
12411
|
+
);
|
|
12412
|
+
}
|
|
12413
|
+
this.#reconcile(false);
|
|
12414
|
+
} catch (/** @type any */ error) {
|
|
12415
|
+
if (claim.stale) {
|
|
12416
|
+
return;
|
|
12417
|
+
}
|
|
12418
|
+
this.#fail(error, { pageRequest: { page: window.first, size }, sortRequest, filterRequest });
|
|
12419
|
+
throw error;
|
|
12420
|
+
}
|
|
12421
|
+
}
|
|
12422
|
+
#update(pageRequest, sortRequest, filterRequest, pageResponse) {
|
|
12423
|
+
this.#loading.setAttribute('hidden', '');
|
|
12424
|
+
if (this.#virtual) {
|
|
12425
|
+
this.#rowHeight = 0;
|
|
12426
|
+
this.#unlockColumns();
|
|
12427
|
+
}
|
|
12428
|
+
const rows = this.#pageRows(pageRequest, filterRequest, pageResponse);
|
|
12429
|
+
this.#body.replaceChildren(...rows);
|
|
11808
12430
|
this.#empty.toggleAttribute('hidden', pageResponse.data.length !== 0);
|
|
11809
12431
|
if (this.#emptyTemplate && pageResponse.data.length === 0) {
|
|
11810
12432
|
this.#emptyAuthors.replaceChildren(
|
|
@@ -11813,7 +12435,28 @@ var fml = (function (exports) {
|
|
|
11813
12435
|
.render(),
|
|
11814
12436
|
);
|
|
11815
12437
|
}
|
|
11816
|
-
this.#
|
|
12438
|
+
if (this.#virtual) {
|
|
12439
|
+
this.#wrapper.scrollTop = 0;
|
|
12440
|
+
this.#total = pageResponse.size;
|
|
12441
|
+
this.#window =
|
|
12442
|
+
rows.length > 0 ? { first: pageRequest.page, last: pageRequest.page, pages: new Map([[pageRequest.page, rows]]) } : null;
|
|
12443
|
+
return;
|
|
12444
|
+
}
|
|
12445
|
+
if (this.#status) {
|
|
12446
|
+
this.#wrapper.scrollTop = 0;
|
|
12447
|
+
this.#appended = {
|
|
12448
|
+
next: pageRequest.page + 1,
|
|
12449
|
+
shown: pageResponse.data.length,
|
|
12450
|
+
total: pageResponse.size,
|
|
12451
|
+
last: pageResponse.data.length,
|
|
12452
|
+
};
|
|
12453
|
+
this.#status.count.textContent = Localization.of().t('table.loaded', {
|
|
12454
|
+
loaded: this.#appended.shown,
|
|
12455
|
+
total: this.#appended.total,
|
|
12456
|
+
});
|
|
12457
|
+
return;
|
|
12458
|
+
}
|
|
12459
|
+
this.#paginator.update({ current: pageRequest.page, total: Math.ceil(pageResponse.size / pageRequest.size) });
|
|
11817
12460
|
}
|
|
11818
12461
|
}
|
|
11819
12462
|
|
|
@@ -14780,6 +15423,9 @@ var fml = (function (exports) {
|
|
|
14780
15423
|
'table.initial': 'Start searching to see results.',
|
|
14781
15424
|
'table.error': 'Error while loading data:',
|
|
14782
15425
|
'table.no-data': 'No elements found.',
|
|
15426
|
+
'table.loaded': 'Showing {loaded} of {total}',
|
|
15427
|
+
'table.range': 'Rows {from}-{to} of {total}',
|
|
15428
|
+
'table.retry': 'Retry',
|
|
14783
15429
|
'dropdown.empty': 'No results',
|
|
14784
15430
|
'select.remove': 'Remove',
|
|
14785
15431
|
'files.dropzone-label': 'Click or drop your files here',
|
|
@@ -14831,6 +15477,9 @@ var fml = (function (exports) {
|
|
|
14831
15477
|
'table.initial': 'Avvia la ricerca per visualizzare i risultati.',
|
|
14832
15478
|
'table.error': 'Errore nel caricamento dei dati:',
|
|
14833
15479
|
'table.no-data': 'Nessun elemento trovato.',
|
|
15480
|
+
'table.loaded': 'Mostrati {loaded} di {total}',
|
|
15481
|
+
'table.range': 'Righe {from}-{to} di {total}',
|
|
15482
|
+
'table.retry': 'Riprova',
|
|
14834
15483
|
'dropdown.empty': 'Nessun risultato',
|
|
14835
15484
|
'select.remove': 'Rimuovi',
|
|
14836
15485
|
'files.dropzone-label': 'Clicca o trascina i file qui',
|
|
@@ -14882,6 +15531,9 @@ var fml = (function (exports) {
|
|
|
14882
15531
|
'table.initial': 'Inicia la búsqueda para ver los resultados.',
|
|
14883
15532
|
'table.error': 'Error al cargar los datos:',
|
|
14884
15533
|
'table.no-data': 'No se encontraron elementos.',
|
|
15534
|
+
'table.loaded': 'Mostrando {loaded} de {total}',
|
|
15535
|
+
'table.range': 'Filas {from}-{to} de {total}',
|
|
15536
|
+
'table.retry': 'Reintentar',
|
|
14885
15537
|
'dropdown.empty': 'Sin resultados',
|
|
14886
15538
|
'select.remove': 'Eliminar',
|
|
14887
15539
|
'files.dropzone-label': 'Haz clic o arrastra tus archivos aquí',
|
|
@@ -14936,6 +15588,9 @@ var fml = (function (exports) {
|
|
|
14936
15588
|
'table.initial': 'Lancez la recherche pour voir les résultats.',
|
|
14937
15589
|
'table.error': 'Erreur lors du chargement des données :',
|
|
14938
15590
|
'table.no-data': 'Aucun élément trouvé.',
|
|
15591
|
+
'table.loaded': 'Affichage de {loaded} sur {total}',
|
|
15592
|
+
'table.range': 'Lignes {from} à {to} sur {total}',
|
|
15593
|
+
'table.retry': 'Réessayer',
|
|
14939
15594
|
'dropdown.empty': 'Aucun résultat',
|
|
14940
15595
|
'select.remove': 'Retirer',
|
|
14941
15596
|
'files.dropzone-label': 'Cliquez ou déposez vos fichiers ici',
|