flexdesk 0.6.1 → 0.6.3
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/README.md +44 -0
- package/css/base.css +43 -0
- package/css/flexdesk.css +43 -0
- package/dist/charts.js +2 -2
- package/dist/{chunk-ZMCRDOCF.js → chunk-WAVLVXYV.js} +2 -2
- package/dist/{chunk-IYV5JVYB.js → chunk-WLASPRGP.js} +328 -20
- package/dist/chunk-WLASPRGP.js.map +7 -0
- package/dist/flexdesk.css +43 -0
- package/dist/flow.js +1 -1
- package/dist/tiles.js +2 -2
- package/dist/widgets.js +8 -2
- package/dist/widgets.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/data_table.js +392 -19
- package/dist/chunk-IYV5JVYB.js.map +0 -7
- /package/dist/{chunk-ZMCRDOCF.js.map → chunk-WAVLVXYV.js.map} +0 -0
package/README.md
CHANGED
|
@@ -354,6 +354,50 @@ lock.setEnabled(false); // scroll each side on its own; true brings them level
|
|
|
354
354
|
position the lock itself wrote moves nobody back. The lock copies pixels, so
|
|
355
355
|
it lines rows up only when both sides draw rows of the same height.
|
|
356
356
|
|
|
357
|
+
### A server's rows in pages or as one list: `paging`
|
|
358
|
+
|
|
359
|
+
`paging` (off unless you set it) shows rows that your code fetches from a
|
|
360
|
+
server either one page at a time or as one list that loads more as the reader
|
|
361
|
+
scrolls. A strip under the body shows which rows are loaded. It has the page
|
|
362
|
+
buttons or a *Load all rows* button, and a Pages / Scroll switch if you pass
|
|
363
|
+
`onModeChange`. Set `paging` and the old pager (`pagination`) is not drawn.
|
|
364
|
+
|
|
365
|
+
```js
|
|
366
|
+
new DataTable(host, {
|
|
367
|
+
headers, rows, // the rows you hold: one page, or all scrolled so far
|
|
368
|
+
virtualize: true,
|
|
369
|
+
paging: {
|
|
370
|
+
mode: 'scroll', // what was asked for: 'pages' or 'scroll'
|
|
371
|
+
pageSize: 1000,
|
|
372
|
+
softLimit: 10000, // above this many rows the table is always paged
|
|
373
|
+
total: 22521, // exact count, or null if unknown
|
|
374
|
+
atLeast: 0, // a lower bound when the count is not known
|
|
375
|
+
offset: 0, // in pages: where rows[0] is in the source
|
|
376
|
+
hasMore: true,
|
|
377
|
+
onPage: (offset, limit) => fetchPage(offset),
|
|
378
|
+
onLoadMore: () => fetchNext(), // called near the end of the scroll
|
|
379
|
+
onLoadAll: () => fetchRest(),
|
|
380
|
+
onModeChange: (mode) => remember(mode),
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Your code holds the rows and fetches them. The table decides how to show them
|
|
386
|
+
with `resolvePaging`, which is exported so your code gets the same answer when
|
|
387
|
+
it decides what to fetch. A source with more rows than `softLimit` is shown in
|
|
388
|
+
pages even if scroll was asked for. The strip says so, and Scroll is disabled
|
|
389
|
+
with the reason. `softLimit` can't be more than `hardLimit` (default and
|
|
390
|
+
ceiling `PAGING_HARD_LIMIT`, 100,000), and a scrolling table never asks for rows
|
|
391
|
+
past it. If no count is known, the rows already loaded count as evidence: a
|
|
392
|
+
table that has loaded more than `softLimit` rows and still has more is paged.
|
|
393
|
+
While scrolling, `onLoadMore` is called within 400px of the end, or after
|
|
394
|
+
render when the rows don't fill the box. It is never called when the box has
|
|
395
|
+
no height, for example in a hidden tab. A callback that returns a Promise
|
|
396
|
+
blocks the next call until it settles, and the strip shows the table busy.
|
|
397
|
+
`setPaging(patch)` redraws the strip only, for example when a count arrives.
|
|
398
|
+
In pages, `showRowNumbers: 'position'` numbers rows from `offset`. Row indexes
|
|
399
|
+
(`__rowIndex`, the selection, `onRowClick`) still refer to positions in `rows`.
|
|
400
|
+
|
|
357
401
|
### Scrollbars
|
|
358
402
|
|
|
359
403
|
`installAutoScrollbars(root)` (from `@flexdesk/widgets`) replaces the browser's
|
package/css/base.css
CHANGED
|
@@ -1648,6 +1648,49 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
1648
1648
|
opacity: 0.3;
|
|
1649
1649
|
cursor: not-allowed;
|
|
1650
1650
|
}
|
|
1651
|
+
/* `paging` — the strip UNDER a paged or scrolling DataTable's body: which rows
|
|
1652
|
+
* are held, the page buttons or Load all, why a large source is paged, and the
|
|
1653
|
+
* Pages / Scroll switch. */
|
|
1654
|
+
.twm-dt-paging {
|
|
1655
|
+
display: flex;
|
|
1656
|
+
align-items: center;
|
|
1657
|
+
gap: var(--space-md);
|
|
1658
|
+
flex: 0 0 auto;
|
|
1659
|
+
min-width: 0;
|
|
1660
|
+
padding: var(--space-xs) var(--space-md);
|
|
1661
|
+
border-top: 1px solid var(--border-subtle);
|
|
1662
|
+
font-size: var(--font-size-sm);
|
|
1663
|
+
color: var(--text-muted);
|
|
1664
|
+
white-space: nowrap;
|
|
1665
|
+
overflow: hidden;
|
|
1666
|
+
}
|
|
1667
|
+
.twm-dt-paging__info { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
1668
|
+
.twm-dt-paging__spacer { flex: 1 1 auto; }
|
|
1669
|
+
.twm-dt-paging__nav { display: inline-flex; align-items: center; gap: var(--space-xs); }
|
|
1670
|
+
.twm-dt-paging__page { min-width: 72px; text-align: center; }
|
|
1671
|
+
.twm-dt-paging .twm-pagination-btn { height: 20px; min-width: 22px; }
|
|
1672
|
+
.twm-dt-paging__note { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; }
|
|
1673
|
+
.twm-dt-paging__all,
|
|
1674
|
+
.twm-dt-paging__mode {
|
|
1675
|
+
height: 20px;
|
|
1676
|
+
padding: 0 var(--space-md);
|
|
1677
|
+
background: transparent;
|
|
1678
|
+
border: 1px solid var(--border-default);
|
|
1679
|
+
border-radius: var(--radius-sm);
|
|
1680
|
+
color: var(--text-body);
|
|
1681
|
+
font: inherit;
|
|
1682
|
+
cursor: pointer;
|
|
1683
|
+
}
|
|
1684
|
+
.twm-dt-paging__all:hover:not(:disabled),
|
|
1685
|
+
.twm-dt-paging__mode:hover:not(:disabled) { border-color: var(--accent); color: var(--text-bright); }
|
|
1686
|
+
.twm-dt-paging__modes { display: inline-flex; }
|
|
1687
|
+
.twm-dt-paging__mode { border-radius: 0; }
|
|
1688
|
+
.twm-dt-paging__mode:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
|
|
1689
|
+
.twm-dt-paging__mode:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin-left: -1px; }
|
|
1690
|
+
.twm-dt-paging__mode[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--text-bright); }
|
|
1691
|
+
.twm-dt-paging__all:disabled,
|
|
1692
|
+
.twm-dt-paging__mode:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
1693
|
+
.twm-dt-paging__mode[aria-pressed="true"]:disabled { opacity: 1; cursor: default; }
|
|
1651
1694
|
/* Preview Table Styles - Essential for data view */
|
|
1652
1695
|
.twm-preview-table-wrap {
|
|
1653
1696
|
overflow: auto;
|
package/css/flexdesk.css
CHANGED
|
@@ -1746,6 +1746,49 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
1746
1746
|
opacity: 0.3;
|
|
1747
1747
|
cursor: not-allowed;
|
|
1748
1748
|
}
|
|
1749
|
+
/* `paging` — the strip UNDER a paged or scrolling DataTable's body: which rows
|
|
1750
|
+
* are held, the page buttons or Load all, why a large source is paged, and the
|
|
1751
|
+
* Pages / Scroll switch. */
|
|
1752
|
+
.twm-dt-paging {
|
|
1753
|
+
display: flex;
|
|
1754
|
+
align-items: center;
|
|
1755
|
+
gap: var(--space-md);
|
|
1756
|
+
flex: 0 0 auto;
|
|
1757
|
+
min-width: 0;
|
|
1758
|
+
padding: var(--space-xs) var(--space-md);
|
|
1759
|
+
border-top: 1px solid var(--border-subtle);
|
|
1760
|
+
font-size: var(--font-size-sm);
|
|
1761
|
+
color: var(--text-muted);
|
|
1762
|
+
white-space: nowrap;
|
|
1763
|
+
overflow: hidden;
|
|
1764
|
+
}
|
|
1765
|
+
.twm-dt-paging__info { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
1766
|
+
.twm-dt-paging__spacer { flex: 1 1 auto; }
|
|
1767
|
+
.twm-dt-paging__nav { display: inline-flex; align-items: center; gap: var(--space-xs); }
|
|
1768
|
+
.twm-dt-paging__page { min-width: 72px; text-align: center; }
|
|
1769
|
+
.twm-dt-paging .twm-pagination-btn { height: 20px; min-width: 22px; }
|
|
1770
|
+
.twm-dt-paging__note { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; }
|
|
1771
|
+
.twm-dt-paging__all,
|
|
1772
|
+
.twm-dt-paging__mode {
|
|
1773
|
+
height: 20px;
|
|
1774
|
+
padding: 0 var(--space-md);
|
|
1775
|
+
background: transparent;
|
|
1776
|
+
border: 1px solid var(--border-default);
|
|
1777
|
+
border-radius: var(--radius-sm);
|
|
1778
|
+
color: var(--text-body);
|
|
1779
|
+
font: inherit;
|
|
1780
|
+
cursor: pointer;
|
|
1781
|
+
}
|
|
1782
|
+
.twm-dt-paging__all:hover:not(:disabled),
|
|
1783
|
+
.twm-dt-paging__mode:hover:not(:disabled) { border-color: var(--accent); color: var(--text-bright); }
|
|
1784
|
+
.twm-dt-paging__modes { display: inline-flex; }
|
|
1785
|
+
.twm-dt-paging__mode { border-radius: 0; }
|
|
1786
|
+
.twm-dt-paging__mode:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
|
|
1787
|
+
.twm-dt-paging__mode:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin-left: -1px; }
|
|
1788
|
+
.twm-dt-paging__mode[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--text-bright); }
|
|
1789
|
+
.twm-dt-paging__all:disabled,
|
|
1790
|
+
.twm-dt-paging__mode:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
1791
|
+
.twm-dt-paging__mode[aria-pressed="true"]:disabled { opacity: 1; cursor: default; }
|
|
1749
1792
|
/* Preview Table Styles - Essential for data view */
|
|
1750
1793
|
.twm-preview-table-wrap {
|
|
1751
1794
|
overflow: auto;
|
package/dist/charts.js
CHANGED
|
@@ -39,10 +39,10 @@ import {
|
|
|
39
39
|
setPlotlySource,
|
|
40
40
|
uid,
|
|
41
41
|
updateChart
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-WAVLVXYV.js";
|
|
43
43
|
import {
|
|
44
44
|
createRafResizeObserver
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-WLASPRGP.js";
|
|
46
46
|
import {
|
|
47
47
|
ManagedWindow
|
|
48
48
|
} from "./chunk-6JSOVNID.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DataTable,
|
|
3
3
|
createRafResizeObserver
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WLASPRGP.js";
|
|
5
5
|
import {
|
|
6
6
|
ManagedWindow
|
|
7
7
|
} from "./chunk-6JSOVNID.js";
|
|
@@ -2370,4 +2370,4 @@ export {
|
|
|
2370
2370
|
openPlotPopoutWindow,
|
|
2371
2371
|
openRawTracesWindow
|
|
2372
2372
|
};
|
|
2373
|
-
//# sourceMappingURL=chunk-
|
|
2373
|
+
//# sourceMappingURL=chunk-WAVLVXYV.js.map
|
|
@@ -361,6 +361,34 @@ function matchDurationFilter(ms, filterText, shown) {
|
|
|
361
361
|
// src/ui/components/data_table.js
|
|
362
362
|
var DEFAULT_PAGE_SIZE = 100;
|
|
363
363
|
var DIFF_STATUSES = /* @__PURE__ */ new Set(["added", "removed", "changed", "same", "gap"]);
|
|
364
|
+
var PAGING_HARD_LIMIT = 1e5;
|
|
365
|
+
var PAGING_SOFT_LIMIT = 1e4;
|
|
366
|
+
function resolvePaging({
|
|
367
|
+
mode = "scroll",
|
|
368
|
+
total = null,
|
|
369
|
+
atLeast = 0,
|
|
370
|
+
held = 0,
|
|
371
|
+
hasMore = false,
|
|
372
|
+
softLimit = PAGING_SOFT_LIMIT,
|
|
373
|
+
hardLimit = PAGING_HARD_LIMIT
|
|
374
|
+
} = {}) {
|
|
375
|
+
const requested = mode === "pages" ? "pages" : "scroll";
|
|
376
|
+
const hard = Number.isFinite(hardLimit) && hardLimit >= 1 ? Math.floor(hardLimit) : PAGING_HARD_LIMIT;
|
|
377
|
+
const soft = Math.min(hard, Number.isFinite(softLimit) && softLimit >= 1 ? Math.floor(softLimit) : PAGING_SOFT_LIMIT);
|
|
378
|
+
const exact = Number.isFinite(total) && total >= 0;
|
|
379
|
+
const known = exact ? total : Math.max(Number.isFinite(atLeast) ? atLeast : 0, (held || 0) + (hasMore ? 1 : 0));
|
|
380
|
+
const forced = requested === "scroll" && known > soft;
|
|
381
|
+
return {
|
|
382
|
+
mode: forced ? "pages" : requested,
|
|
383
|
+
requested,
|
|
384
|
+
forced,
|
|
385
|
+
softLimit: soft,
|
|
386
|
+
hardLimit: hard,
|
|
387
|
+
known,
|
|
388
|
+
exact
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
var PAGING_LOAD_MORE_PX = 400;
|
|
364
392
|
var TYPED_COLUMNS = /* @__PURE__ */ new Set(["date", "datetime", "duration"]);
|
|
365
393
|
var DESELECTING_CLICKS = /* @__PURE__ */ new WeakSet();
|
|
366
394
|
var AUTO_DISPOSE = /* @__PURE__ */ new Map();
|
|
@@ -513,6 +541,9 @@ var DataTable = class {
|
|
|
513
541
|
// promise that adding the key changed no existing layout by a
|
|
514
542
|
// pixel. Only a consumer that asks for 'content' sees anything new.
|
|
515
543
|
columnFit: "container",
|
|
544
|
+
// Off unless asked for — see the typedef. With it the old pager is
|
|
545
|
+
// not drawn: the paging strip says the same and more.
|
|
546
|
+
paging: null,
|
|
516
547
|
// C10. Off unless asked for — see the typedef.
|
|
517
548
|
virtualize: false,
|
|
518
549
|
virtualRowHeight: null,
|
|
@@ -925,7 +956,7 @@ var DataTable = class {
|
|
|
925
956
|
this._wrapperEl.style.cssText = "display:flex; flex-direction:column; height:100%; min-height:0;";
|
|
926
957
|
}
|
|
927
958
|
this._loading = false;
|
|
928
|
-
if (pagination && rows.length > 0) {
|
|
959
|
+
if (pagination && rows.length > 0 && !this._pagingConfig()) {
|
|
929
960
|
this._paginationEl = this._createPagination();
|
|
930
961
|
if (this._paginationEl) this._wrapperEl.appendChild(this._paginationEl);
|
|
931
962
|
}
|
|
@@ -1011,6 +1042,11 @@ var DataTable = class {
|
|
|
1011
1042
|
this._wrapperEl.appendChild(tableWrap);
|
|
1012
1043
|
}
|
|
1013
1044
|
this._tableWrapEl = tableWrap;
|
|
1045
|
+
this._pagingEl = null;
|
|
1046
|
+
if (this._pagingConfig()) {
|
|
1047
|
+
this._pagingEl = this._createPagingStrip();
|
|
1048
|
+
if (this._pagingEl) this._wrapperEl.appendChild(this._pagingEl);
|
|
1049
|
+
}
|
|
1014
1050
|
this.container.appendChild(this._wrapperEl);
|
|
1015
1051
|
if (this.config.scrollLock && typeof this.config.scrollLock.attach === "function") {
|
|
1016
1052
|
this._disposers.push(this.config.scrollLock.attach(tableWrap));
|
|
@@ -1028,8 +1064,19 @@ var DataTable = class {
|
|
|
1028
1064
|
this._virtMeasure();
|
|
1029
1065
|
this._virtUpdate({ force: true, notify: false });
|
|
1030
1066
|
}
|
|
1067
|
+
if (this._pagingConfig()) {
|
|
1068
|
+
const onScroll = () => this._pagingMaybeLoadMore();
|
|
1069
|
+
tableWrap.addEventListener("scroll", onScroll, { passive: true });
|
|
1070
|
+
this._disposers.push(() => tableWrap.removeEventListener("scroll", onScroll));
|
|
1071
|
+
requestAnimationFrame(() => {
|
|
1072
|
+
if (this._tableWrapEl === tableWrap) this._pagingMaybeLoadMore();
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1031
1075
|
if (this._headerTableEl && this._tableEl) {
|
|
1032
|
-
requestAnimationFrame(() =>
|
|
1076
|
+
requestAnimationFrame(() => {
|
|
1077
|
+
this._syncHeaderWidths();
|
|
1078
|
+
this._virtRemeasure();
|
|
1079
|
+
});
|
|
1033
1080
|
if (this._resizeObserver) {
|
|
1034
1081
|
try {
|
|
1035
1082
|
this._resizeObserver.disconnect();
|
|
@@ -1890,13 +1937,234 @@ var DataTable = class {
|
|
|
1890
1937
|
btn.addEventListener("click", onClick);
|
|
1891
1938
|
return btn;
|
|
1892
1939
|
}
|
|
1940
|
+
// ── `paging` ────────────────────────────────────────────────────────────
|
|
1941
|
+
/** The `paging` config with its defaults, or null when it is not set. */
|
|
1942
|
+
_pagingConfig() {
|
|
1943
|
+
const p = this.config.paging;
|
|
1944
|
+
if (!p || typeof p !== "object") return null;
|
|
1945
|
+
const size = Number.isFinite(p.pageSize) && p.pageSize >= 1 ? Math.floor(p.pageSize) : this.config.pageSize;
|
|
1946
|
+
return {
|
|
1947
|
+
...p,
|
|
1948
|
+
pageSize: size,
|
|
1949
|
+
offset: Number.isFinite(p.offset) && p.offset > 0 ? Math.floor(p.offset) : 0,
|
|
1950
|
+
// What the table holds of the source. A consumer whose `rows` carry
|
|
1951
|
+
// a row the source does not (a blank line to type into) says so.
|
|
1952
|
+
held: Number.isFinite(p.held) ? p.held : this.config.rows?.length || 0,
|
|
1953
|
+
hasMore: !!p.hasMore,
|
|
1954
|
+
loadMorePx: Number.isFinite(p.loadMorePx) ? p.loadMorePx : PAGING_LOAD_MORE_PX
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Which way the rows are shown, and why — `resolvePaging` over this
|
|
1959
|
+
* table's facts. `null` without `paging`.
|
|
1960
|
+
*/
|
|
1961
|
+
get pagingState() {
|
|
1962
|
+
const p = this._pagingConfig();
|
|
1963
|
+
if (!p) return null;
|
|
1964
|
+
return resolvePaging({
|
|
1965
|
+
mode: p.mode,
|
|
1966
|
+
total: p.total,
|
|
1967
|
+
atLeast: p.atLeast,
|
|
1968
|
+
held: p.offset + p.held,
|
|
1969
|
+
hasMore: p.hasMore,
|
|
1970
|
+
softLimit: p.softLimit,
|
|
1971
|
+
hardLimit: p.hardLimit
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Change some `paging` facts — a count that arrived, a mode — and redraw
|
|
1976
|
+
* the STRIP only. The rows are not touched: a consumer with new rows
|
|
1977
|
+
* renders the table as it always has.
|
|
1978
|
+
*/
|
|
1979
|
+
setPaging(patch = {}) {
|
|
1980
|
+
if (!this.config.paging) return;
|
|
1981
|
+
this.config.paging = { ...this.config.paging, ...patch };
|
|
1982
|
+
this._refreshPagingStrip();
|
|
1983
|
+
this._pagingMaybeLoadMore();
|
|
1984
|
+
}
|
|
1985
|
+
_refreshPagingStrip() {
|
|
1986
|
+
if (!this._wrapperEl) return;
|
|
1987
|
+
const next = this._pagingConfig() ? this._createPagingStrip() : null;
|
|
1988
|
+
const prev = this._pagingEl;
|
|
1989
|
+
if (prev && prev.parentNode === this._wrapperEl) {
|
|
1990
|
+
if (next) prev.replaceWith(next);
|
|
1991
|
+
else prev.remove();
|
|
1992
|
+
} else if (next) {
|
|
1993
|
+
this._wrapperEl.appendChild(next);
|
|
1994
|
+
}
|
|
1995
|
+
this._pagingEl = next;
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Call one of the consumer's paging callbacks. A Promise holds every other
|
|
1999
|
+
* paging call back until it settles, and the strip shows the table busy —
|
|
2000
|
+
* a second `onLoadMore` fired by the next scroll event would fetch the same
|
|
2001
|
+
* page twice.
|
|
2002
|
+
*/
|
|
2003
|
+
_pagingCall(kind, fn, ...args) {
|
|
2004
|
+
if (typeof fn !== "function" || this._pagingBusy) return;
|
|
2005
|
+
let result;
|
|
2006
|
+
try {
|
|
2007
|
+
result = fn(...args);
|
|
2008
|
+
} catch (err) {
|
|
2009
|
+
console.error(`[DataTable] paging ${kind} threw`, err);
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
if (result && typeof result.then === "function") {
|
|
2013
|
+
this._pagingBusy = kind;
|
|
2014
|
+
this._refreshPagingStrip();
|
|
2015
|
+
const settle = () => {
|
|
2016
|
+
if (this._pagingBusy !== kind) return;
|
|
2017
|
+
this._pagingBusy = null;
|
|
2018
|
+
if (this._pagingEl?.isConnected) this._refreshPagingStrip();
|
|
2019
|
+
};
|
|
2020
|
+
result.then(settle, (err) => {
|
|
2021
|
+
console.error(`[DataTable] paging ${kind} failed`, err);
|
|
2022
|
+
settle();
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Scrolling: ask for the next rows when the body is near its end — or does
|
|
2028
|
+
* not fill its box, which no scroll can ever change. Never past the hard
|
|
2029
|
+
* limit, never in pages, never with no layout (a hidden table's box is 0px
|
|
2030
|
+
* tall, and "0 ≤ 0" would ask for the whole source).
|
|
2031
|
+
*/
|
|
2032
|
+
_pagingMaybeLoadMore() {
|
|
2033
|
+
const p = this._pagingConfig();
|
|
2034
|
+
if (!p || !p.hasMore || this._pagingBusy) return;
|
|
2035
|
+
const state = this.pagingState;
|
|
2036
|
+
if (state.mode !== "scroll" || p.held >= state.hardLimit) return;
|
|
2037
|
+
const wrap = this._tableWrapEl;
|
|
2038
|
+
if (!wrap?.isConnected || !(wrap.clientHeight > 0)) return;
|
|
2039
|
+
const remaining = wrap.scrollHeight - wrap.scrollTop - wrap.clientHeight;
|
|
2040
|
+
if (remaining >= p.loadMorePx) return;
|
|
2041
|
+
this._pagingCall("more", p.onLoadMore);
|
|
2042
|
+
}
|
|
2043
|
+
/** The strip under the body, or null when it would say nothing. */
|
|
2044
|
+
_createPagingStrip() {
|
|
2045
|
+
const p = this._pagingConfig();
|
|
2046
|
+
const state = this.pagingState;
|
|
2047
|
+
const { mode, forced, exact, known } = state;
|
|
2048
|
+
const busy = this._pagingBusy;
|
|
2049
|
+
const n = (x) => Number(x).toLocaleString();
|
|
2050
|
+
const pageSize = p.pageSize;
|
|
2051
|
+
const held = p.held;
|
|
2052
|
+
const total = exact ? p.total : null;
|
|
2053
|
+
const morePages = p.offset > 0 || p.hasMore || exact && total > held;
|
|
2054
|
+
if (!forced && mode === "scroll" && !p.hasMore && held <= pageSize) return null;
|
|
2055
|
+
if (!forced && mode === "pages" && !morePages) return null;
|
|
2056
|
+
const el = document.createElement("div");
|
|
2057
|
+
el.className = `twm-dt-paging twm-dt-paging--${mode}`;
|
|
2058
|
+
el.setAttribute("role", "navigation");
|
|
2059
|
+
el.setAttribute("aria-label", "Rows");
|
|
2060
|
+
if (busy) el.classList.add("twm-dt-paging--busy");
|
|
2061
|
+
const info = document.createElement("span");
|
|
2062
|
+
info.className = "twm-dt-paging__info";
|
|
2063
|
+
el.appendChild(info);
|
|
2064
|
+
if (mode === "pages") {
|
|
2065
|
+
const from = held ? p.offset + 1 : p.offset;
|
|
2066
|
+
const to = p.offset + held;
|
|
2067
|
+
let of = "";
|
|
2068
|
+
if (exact) of = ` of ${n(total)}`;
|
|
2069
|
+
else if (known > to) of = ` of more than ${n(known - 1)}`;
|
|
2070
|
+
info.textContent = held ? `Rows ${n(from)}\u2013${n(to)}${of}` : "No rows on this page";
|
|
2071
|
+
const hasPrev = p.offset > 0;
|
|
2072
|
+
const hasNext = exact ? p.offset + held < total : p.hasMore;
|
|
2073
|
+
const lastOffset = exact && total > 0 ? Math.floor((total - 1) / pageSize) * pageSize : null;
|
|
2074
|
+
const go = (offset) => (ev) => {
|
|
2075
|
+
if (!ev?.currentTarget?.disabled) this._pagingCall("page", p.onPage, offset, pageSize);
|
|
2076
|
+
};
|
|
2077
|
+
const nav = document.createElement("span");
|
|
2078
|
+
nav.className = "twm-dt-paging__nav";
|
|
2079
|
+
const off = !!busy || typeof p.onPage !== "function";
|
|
2080
|
+
nav.append(
|
|
2081
|
+
this._createPaginationBtn("first_page", "First page", off || !hasPrev, go(0)),
|
|
2082
|
+
this._createPaginationBtn(
|
|
2083
|
+
"chevron_left",
|
|
2084
|
+
"Previous page",
|
|
2085
|
+
off || !hasPrev,
|
|
2086
|
+
go(Math.max(0, p.offset - pageSize))
|
|
2087
|
+
)
|
|
2088
|
+
);
|
|
2089
|
+
const page = document.createElement("span");
|
|
2090
|
+
page.className = "twm-dt-paging__page";
|
|
2091
|
+
const current = Math.floor(p.offset / pageSize) + 1;
|
|
2092
|
+
page.textContent = exact ? `Page ${n(current)} of ${n(Math.max(1, Math.ceil(total / pageSize)))}` : `Page ${n(current)}`;
|
|
2093
|
+
nav.append(
|
|
2094
|
+
page,
|
|
2095
|
+
this._createPaginationBtn(
|
|
2096
|
+
"chevron_right",
|
|
2097
|
+
"Next page",
|
|
2098
|
+
off || !hasNext,
|
|
2099
|
+
go(p.offset + pageSize)
|
|
2100
|
+
),
|
|
2101
|
+
// The last page needs the exact count: without it there is no
|
|
2102
|
+
// offset to go to, and the button says so by being off.
|
|
2103
|
+
this._createPaginationBtn(
|
|
2104
|
+
"last_page",
|
|
2105
|
+
lastOffset === null ? "Last page (the total is not known)" : "Last page",
|
|
2106
|
+
off || !hasNext || lastOffset === null,
|
|
2107
|
+
go(lastOffset)
|
|
2108
|
+
)
|
|
2109
|
+
);
|
|
2110
|
+
el.appendChild(nav);
|
|
2111
|
+
} else {
|
|
2112
|
+
if (exact && total > held) info.textContent = `${n(held)} of ${n(total)} rows`;
|
|
2113
|
+
else if (p.hasMore) info.textContent = `${n(held)} rows, more to come`;
|
|
2114
|
+
else info.textContent = `${n(held)} rows`;
|
|
2115
|
+
if (p.hasMore && typeof p.onLoadAll === "function") {
|
|
2116
|
+
const all = document.createElement("button");
|
|
2117
|
+
all.type = "button";
|
|
2118
|
+
all.className = "twm-dt-paging__all";
|
|
2119
|
+
all.textContent = busy === "all" ? "Loading\u2026" : exact ? `Load all ${n(total)} rows` : "Load all rows";
|
|
2120
|
+
all.disabled = !!busy;
|
|
2121
|
+
all.addEventListener("click", () => {
|
|
2122
|
+
if (!all.disabled) this._pagingCall("all", p.onLoadAll);
|
|
2123
|
+
});
|
|
2124
|
+
el.appendChild(all);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
const spacer = document.createElement("span");
|
|
2128
|
+
spacer.className = "twm-dt-paging__spacer";
|
|
2129
|
+
el.appendChild(spacer);
|
|
2130
|
+
const why = forced ? `More than ${n(state.softLimit)} rows, so they are shown in pages.` : null;
|
|
2131
|
+
if (why) {
|
|
2132
|
+
const note = document.createElement("span");
|
|
2133
|
+
note.className = "twm-dt-paging__note";
|
|
2134
|
+
note.textContent = why;
|
|
2135
|
+
el.appendChild(note);
|
|
2136
|
+
}
|
|
2137
|
+
if (typeof p.onModeChange === "function") {
|
|
2138
|
+
const group = document.createElement("span");
|
|
2139
|
+
group.className = "twm-dt-paging__modes";
|
|
2140
|
+
group.setAttribute("role", "group");
|
|
2141
|
+
group.setAttribute("aria-label", "Show rows");
|
|
2142
|
+
for (const [value, label] of [["pages", "Pages"], ["scroll", "Scroll"]]) {
|
|
2143
|
+
const b = document.createElement("button");
|
|
2144
|
+
b.type = "button";
|
|
2145
|
+
b.className = "twm-dt-paging__mode";
|
|
2146
|
+
b.dataset.mode = value;
|
|
2147
|
+
b.textContent = label;
|
|
2148
|
+
const pressed = mode === value;
|
|
2149
|
+
b.setAttribute("aria-pressed", String(pressed));
|
|
2150
|
+
b.disabled = !!busy || value === "scroll" && forced;
|
|
2151
|
+
if (value === "scroll" && forced) b.title = why;
|
|
2152
|
+
b.addEventListener("click", () => {
|
|
2153
|
+
if (!b.disabled && mode !== value) this._pagingCall("mode", p.onModeChange, value);
|
|
2154
|
+
});
|
|
2155
|
+
group.appendChild(b);
|
|
2156
|
+
}
|
|
2157
|
+
el.appendChild(group);
|
|
2158
|
+
}
|
|
2159
|
+
return el;
|
|
2160
|
+
}
|
|
1893
2161
|
_createTable() {
|
|
1894
2162
|
const { headers, rows, pageSize, sortable, filterable, showRowNumbers, readonly, getHeaderIcon, onPageChange, offset: configOffset } = this.config;
|
|
1895
2163
|
const { sortColumn, sortAscending } = this._state;
|
|
1896
2164
|
const isServerSide = typeof onPageChange === "function";
|
|
1897
2165
|
const processedRows = isServerSide ? rows : this._getProcessedRows();
|
|
1898
2166
|
const offset = isServerSide ? configOffset || 0 : this._state.offset;
|
|
1899
|
-
const pageRows = isServerSide || !this.config.pagination ? processedRows : processedRows.slice(offset, offset + pageSize);
|
|
2167
|
+
const pageRows = isServerSide || !this.config.pagination || this._pagingConfig() ? processedRows : processedRows.slice(offset, offset + pageSize);
|
|
1900
2168
|
const table = document.createElement("table");
|
|
1901
2169
|
table.className = this._tableClassName();
|
|
1902
2170
|
table.tabIndex = 0;
|
|
@@ -1959,6 +2227,8 @@ var DataTable = class {
|
|
|
1959
2227
|
return isServerSide ? (configOffset || 0) + localIdx : this._processedIndexMap?.[processedIdx] ?? processedIdx;
|
|
1960
2228
|
};
|
|
1961
2229
|
this._positionBase = isServerSide ? configOffset || 0 : this.config.pagination ? offset : 0;
|
|
2230
|
+
const paged = this._pagingConfig();
|
|
2231
|
+
if (paged && Number.isFinite(paged.offset)) this._positionBase += paged.offset;
|
|
1962
2232
|
const tbody = document.createElement("tbody");
|
|
1963
2233
|
table.appendChild(tbody);
|
|
1964
2234
|
this._tbodyEl = tbody;
|
|
@@ -2144,26 +2414,51 @@ var DataTable = class {
|
|
|
2144
2414
|
}
|
|
2145
2415
|
return lo;
|
|
2146
2416
|
}
|
|
2147
|
-
/**
|
|
2148
|
-
*
|
|
2149
|
-
*
|
|
2417
|
+
/**
|
|
2418
|
+
* Measure the rows that are DRAWN — the median of up to 64 of them. Client
|
|
2419
|
+
* pixels, so the zoom is divided back out (`_measureNaturalWidths` explains
|
|
2420
|
+
* why), and NOT `offsetHeight`, which is rounded: 21 for a 21.19px row is a
|
|
2421
|
+
* 1,100px error over 6,000 rows.
|
|
2422
|
+
*
|
|
2423
|
+
* 0.6.3: IT USED TO MEASURE ONE ROW, ONCE, AT RENDER, and trust it for the
|
|
2424
|
+
* life of the table. A row measured at a bad moment — a box not yet laid
|
|
2425
|
+
* out at its width, a font not yet loaded — set every row's height: a
|
|
2426
|
+
* Tables grid drawing 22.8px rows was found holding a 134px estimate, so the
|
|
2427
|
+
* window took a 650px viewport for five rows, drew rows 1–35 and left the
|
|
2428
|
+
* rest of the box blank spacer while the reader scrolled. Now it is asked
|
|
2429
|
+
* again on every window update (`_virtUpdate`), on a resize and once the
|
|
2430
|
+
* column widths are fixed, and corrects itself the first time real rows
|
|
2431
|
+
* are on screen. The first measure is exact; after that a change has to be
|
|
2432
|
+
* larger than half a pixel, so rows of mixed heights cannot make the
|
|
2433
|
+
* offsets move under the reader on every scroll.
|
|
2434
|
+
*
|
|
2435
|
+
* @returns {boolean} whether the heights changed (the offsets are recomputed).
|
|
2436
|
+
*/
|
|
2150
2437
|
_virtMeasure() {
|
|
2151
2438
|
const v = this._virt;
|
|
2152
2439
|
if (!v) return false;
|
|
2440
|
+
const zoom = this._tableEl?.currentCSSZoom || 1;
|
|
2441
|
+
const heights = [];
|
|
2153
2442
|
for (const tr of v.map.values()) {
|
|
2154
2443
|
if (!tr.isConnected || tr.hidden) continue;
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
if (
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2444
|
+
const h2 = tr.getBoundingClientRect().height / zoom;
|
|
2445
|
+
if (h2 > 0) heights.push(h2);
|
|
2446
|
+
if (heights.length >= 64) break;
|
|
2447
|
+
}
|
|
2448
|
+
if (!heights.length) return false;
|
|
2449
|
+
heights.sort((a, b) => a - b);
|
|
2450
|
+
const h = heights[heights.length >> 1];
|
|
2451
|
+
const tolerance = v.measured ? 0.5 : 0.01;
|
|
2452
|
+
v.measured = true;
|
|
2453
|
+
if (Math.abs(h - v.base) <= tolerance) return false;
|
|
2454
|
+
v.base = h;
|
|
2455
|
+
this._virtBase = h;
|
|
2456
|
+
this._virtComputeOffsets();
|
|
2457
|
+
return true;
|
|
2458
|
+
}
|
|
2459
|
+
/** Measure again, and redraw the window when the heights moved. */
|
|
2460
|
+
_virtRemeasure() {
|
|
2461
|
+
if (this._virtMeasure()) this._virtUpdate({ force: true });
|
|
2167
2462
|
}
|
|
2168
2463
|
/**
|
|
2169
2464
|
* Slide the window to wherever the scroll box now is.
|
|
@@ -2182,9 +2477,10 @@ var DataTable = class {
|
|
|
2182
2477
|
* @param {number} [o.viewport] the viewport height, likewise.
|
|
2183
2478
|
* @returns {HTMLElement[]} the rows added.
|
|
2184
2479
|
*/
|
|
2185
|
-
_virtUpdate({ force = false, notify = true, top = null, viewport = null } = {}) {
|
|
2480
|
+
_virtUpdate({ force = false, notify = true, top = null, viewport = null, measure = true } = {}) {
|
|
2186
2481
|
const v = this._virt;
|
|
2187
2482
|
if (!v || !v.offsets) return [];
|
|
2483
|
+
if (measure && this._virtMeasure()) force = true;
|
|
2188
2484
|
const n = v.rows.length;
|
|
2189
2485
|
const wrap = this._tableWrapEl;
|
|
2190
2486
|
const view = viewport || wrap?.clientHeight || VIRTUAL_FALLBACK_VIEWPORT_PX;
|
|
@@ -2212,6 +2508,15 @@ var DataTable = class {
|
|
|
2212
2508
|
console.error("[DataTable] onRowsRendered threw", err);
|
|
2213
2509
|
}
|
|
2214
2510
|
}
|
|
2511
|
+
if (measure && added.length && this._virtMeasure()) {
|
|
2512
|
+
return added.concat(this._virtUpdate({
|
|
2513
|
+
force: true,
|
|
2514
|
+
notify,
|
|
2515
|
+
top,
|
|
2516
|
+
viewport,
|
|
2517
|
+
measure: false
|
|
2518
|
+
}));
|
|
2519
|
+
}
|
|
2215
2520
|
}
|
|
2216
2521
|
return added;
|
|
2217
2522
|
}
|
|
@@ -3725,6 +4030,9 @@ export {
|
|
|
3725
4030
|
formatDuration,
|
|
3726
4031
|
matchDateFilter,
|
|
3727
4032
|
matchDurationFilter,
|
|
4033
|
+
PAGING_HARD_LIMIT,
|
|
4034
|
+
PAGING_SOFT_LIMIT,
|
|
4035
|
+
resolvePaging,
|
|
3728
4036
|
DataTable
|
|
3729
4037
|
};
|
|
3730
|
-
//# sourceMappingURL=chunk-
|
|
4038
|
+
//# sourceMappingURL=chunk-WLASPRGP.js.map
|