@pallasoft/ps-table-components 1.26.0 → 1.27.1
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/components/ps-data-table.d.ts +9 -1
- package/dist/components/ps-data-table.js +81 -12
- package/dist/components/ps-select-all-matching-banner.d.ts +10 -0
- package/dist/components/ps-select-all-matching-banner.js +10 -0
- package/dist/components/ps-table-pagination.d.ts +3 -1
- package/dist/components/ps-table-pagination.js +12 -4
- package/dist/constants/ps-server-rows-config.d.ts +8 -0
- package/dist/constants/ps-server-rows-config.js +11 -0
- package/dist/constants/ps-table-pagination-config.d.ts +3 -0
- package/dist/constants/ps-table-pagination-config.js +6 -0
- package/dist/esm/components/ps-data-table.js +82 -13
- package/dist/esm/components/ps-select-all-matching-banner.js +6 -0
- package/dist/esm/components/ps-table-pagination.js +12 -4
- package/dist/esm/constants/ps-server-rows-config.js +8 -0
- package/dist/esm/constants/ps-table-pagination-config.js +3 -0
- package/dist/esm/hooks/use-ps-server-rows.js +209 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/interfaces/ps-server-entry.js +1 -0
- package/dist/esm/interfaces/ps-server-page.js +1 -0
- package/dist/esm/interfaces/ps-server-rows-input.js +1 -0
- package/dist/esm/interfaces/ps-server-rows-result.js +1 -0
- package/dist/esm/interfaces/ps-table-cursor-paging.js +1 -0
- package/dist/esm/interfaces/ps-table-select-all-matching.js +1 -0
- package/dist/esm/services/ps-server-rows-service.js +36 -0
- package/dist/hooks/use-ps-server-rows.d.ts +3 -0
- package/dist/hooks/use-ps-server-rows.js +213 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/interfaces/ps-server-entry.d.ts +6 -0
- package/dist/interfaces/ps-server-entry.js +2 -0
- package/dist/interfaces/ps-server-page.d.ts +7 -0
- package/dist/interfaces/ps-server-page.js +2 -0
- package/dist/interfaces/ps-server-rows-input.d.ts +13 -0
- package/dist/interfaces/ps-server-rows-input.js +2 -0
- package/dist/interfaces/ps-server-rows-result.d.ts +18 -0
- package/dist/interfaces/ps-server-rows-result.js +2 -0
- package/dist/interfaces/ps-table-cursor-paging.d.ts +12 -0
- package/dist/interfaces/ps-table-cursor-paging.js +2 -0
- package/dist/interfaces/ps-table-select-all-matching.d.ts +7 -0
- package/dist/interfaces/ps-table-select-all-matching.js +2 -0
- package/dist/services/ps-server-rows-service.d.ts +11 -0
- package/dist/services/ps-server-rows-service.js +40 -0
- package/package.json +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { PsServerRowsConfig } from '../constants/ps-server-rows-config';
|
|
3
|
+
import { PsServerRowsService } from '../services/ps-server-rows-service';
|
|
4
|
+
const scheduleIdle = (run) => {
|
|
5
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
6
|
+
const handle = window.requestIdleCallback(run, { timeout: PsServerRowsConfig.idleTimeoutMs });
|
|
7
|
+
return () => window.cancelIdleCallback(handle);
|
|
8
|
+
}
|
|
9
|
+
const timer = window.setTimeout(run, PsServerRowsConfig.idleFallbackMs);
|
|
10
|
+
return () => window.clearTimeout(timer);
|
|
11
|
+
};
|
|
12
|
+
export const usePsServerRows = ({ columns, debounceMs = PsServerRowsConfig.debounceMs, getSearchText, initialPage = null, initialQuery, isPrefetching = true, load, pageSize, }) => {
|
|
13
|
+
const [query, setQuery] = useState(initialQuery);
|
|
14
|
+
const [search, setSearch] = useState(initialQuery.search);
|
|
15
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
16
|
+
const [isLoading, setIsLoading] = useState(initialPage === null);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const queryKey = PsServerRowsService.queryKey(query, search, pageSize);
|
|
19
|
+
const [navigation, setNavigation] = useState({
|
|
20
|
+
cursor: null,
|
|
21
|
+
pageNumber: 1,
|
|
22
|
+
queryKey,
|
|
23
|
+
});
|
|
24
|
+
const [entry, setEntry] = useState(() => initialPage === null
|
|
25
|
+
? null
|
|
26
|
+
: {
|
|
27
|
+
key: PsServerRowsService.cacheKey(queryKey, null),
|
|
28
|
+
page: initialPage,
|
|
29
|
+
queryKey,
|
|
30
|
+
});
|
|
31
|
+
const cache = useRef(new Map());
|
|
32
|
+
const inflight = useRef(new Map());
|
|
33
|
+
const loadRef = useRef(load);
|
|
34
|
+
const limitRef = useRef(pageSize);
|
|
35
|
+
const queryRef = useRef(query);
|
|
36
|
+
const searchRef = useRef(search);
|
|
37
|
+
const isPrimed = useRef(false);
|
|
38
|
+
loadRef.current = load;
|
|
39
|
+
limitRef.current = pageSize;
|
|
40
|
+
queryRef.current = query;
|
|
41
|
+
searchRef.current = search;
|
|
42
|
+
if (initialPage !== null && !isPrimed.current) {
|
|
43
|
+
isPrimed.current = true;
|
|
44
|
+
PsServerRowsService.remember(cache.current, PsServerRowsService.cacheKey(queryKey, null), initialPage);
|
|
45
|
+
}
|
|
46
|
+
if (navigation.queryKey !== queryKey)
|
|
47
|
+
setNavigation({ cursor: null, pageNumber: 1, queryKey });
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (query.search === search)
|
|
50
|
+
return;
|
|
51
|
+
const timer = window.setTimeout(() => setSearch(query.search), debounceMs);
|
|
52
|
+
return () => window.clearTimeout(timer);
|
|
53
|
+
}, [debounceMs, query.search, search]);
|
|
54
|
+
const fetchPage = useCallback((cursor, signal, key) => {
|
|
55
|
+
const pending = inflight.current.get(key);
|
|
56
|
+
if (pending !== undefined)
|
|
57
|
+
return pending;
|
|
58
|
+
const request = loadRef.current({ ...queryRef.current, search: searchRef.current }, cursor, signal, limitRef.current);
|
|
59
|
+
const tracked = request.then((page) => {
|
|
60
|
+
inflight.current.delete(key);
|
|
61
|
+
if (!signal.aborted)
|
|
62
|
+
PsServerRowsService.remember(cache.current, key, page);
|
|
63
|
+
return page;
|
|
64
|
+
}, (failure) => {
|
|
65
|
+
inflight.current.delete(key);
|
|
66
|
+
throw failure;
|
|
67
|
+
});
|
|
68
|
+
inflight.current.set(key, tracked);
|
|
69
|
+
return tracked;
|
|
70
|
+
}, []);
|
|
71
|
+
const generation = useRef(null);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
generation.current = controller;
|
|
75
|
+
return () => {
|
|
76
|
+
controller.abort();
|
|
77
|
+
inflight.current.clear();
|
|
78
|
+
};
|
|
79
|
+
}, [queryKey, reloadToken]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const controller = generation.current;
|
|
82
|
+
if (controller === null)
|
|
83
|
+
return;
|
|
84
|
+
const key = PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
85
|
+
const cached = PsServerRowsService.recall(cache.current, key);
|
|
86
|
+
let isStale = false;
|
|
87
|
+
if (cached !== undefined) {
|
|
88
|
+
setEntry({ key, page: cached, queryKey });
|
|
89
|
+
setIsLoading(false);
|
|
90
|
+
setError(null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setIsLoading(true);
|
|
94
|
+
setError(null);
|
|
95
|
+
fetchPage(navigation.cursor, controller.signal, key).then((page) => {
|
|
96
|
+
if (isStale || controller.signal.aborted)
|
|
97
|
+
return;
|
|
98
|
+
setEntry({ key, page, queryKey });
|
|
99
|
+
setIsLoading(false);
|
|
100
|
+
}, (failure) => {
|
|
101
|
+
if (isStale || controller.signal.aborted)
|
|
102
|
+
return;
|
|
103
|
+
setError(failure);
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
});
|
|
106
|
+
return () => {
|
|
107
|
+
isStale = true;
|
|
108
|
+
};
|
|
109
|
+
}, [fetchPage, navigation.cursor, queryKey, reloadToken]);
|
|
110
|
+
const nextCursor = entry !== null && entry.queryKey === queryKey ? entry.page.nextCursor : null;
|
|
111
|
+
const prefetch = useCallback(() => {
|
|
112
|
+
const controller = generation.current;
|
|
113
|
+
if (controller === null || controller.signal.aborted || nextCursor === null)
|
|
114
|
+
return;
|
|
115
|
+
const key = PsServerRowsService.cacheKey(queryKey, nextCursor);
|
|
116
|
+
if (cache.current.has(key) || inflight.current.has(key))
|
|
117
|
+
return;
|
|
118
|
+
fetchPage(nextCursor, controller.signal, key).catch(() => undefined);
|
|
119
|
+
}, [fetchPage, nextCursor, queryKey]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!isPrefetching || isLoading || nextCursor === null)
|
|
122
|
+
return;
|
|
123
|
+
return scheduleIdle(prefetch);
|
|
124
|
+
}, [isLoading, isPrefetching, nextCursor, prefetch]);
|
|
125
|
+
const onQueryChange = useCallback((next) => setQuery(next), []);
|
|
126
|
+
const goNext = useCallback(() => {
|
|
127
|
+
if (nextCursor === null)
|
|
128
|
+
return;
|
|
129
|
+
setNavigation((current) => ({
|
|
130
|
+
...current,
|
|
131
|
+
cursor: nextCursor,
|
|
132
|
+
pageNumber: current.pageNumber + 1,
|
|
133
|
+
}));
|
|
134
|
+
}, [nextCursor]);
|
|
135
|
+
const previousCursor = entry !== null && entry.queryKey === queryKey ? entry.page.prevCursor : null;
|
|
136
|
+
const goPrevious = useCallback(() => {
|
|
137
|
+
if (previousCursor === null)
|
|
138
|
+
return;
|
|
139
|
+
setNavigation((current) => ({
|
|
140
|
+
...current,
|
|
141
|
+
cursor: previousCursor,
|
|
142
|
+
pageNumber: Math.max(current.pageNumber - 1, 1),
|
|
143
|
+
}));
|
|
144
|
+
}, [previousCursor]);
|
|
145
|
+
const reload = useCallback(() => {
|
|
146
|
+
cache.current.clear();
|
|
147
|
+
setNavigation((current) => ({ ...current, cursor: null, pageNumber: 1 }));
|
|
148
|
+
setReloadToken((current) => current + 1);
|
|
149
|
+
}, []);
|
|
150
|
+
const retry = useCallback(() => setReloadToken((current) => current + 1), []);
|
|
151
|
+
const updateRows = useCallback((update, totalDelta = 0) => {
|
|
152
|
+
setEntry((current) => {
|
|
153
|
+
if (current === null)
|
|
154
|
+
return current;
|
|
155
|
+
const page = {
|
|
156
|
+
...current.page,
|
|
157
|
+
rows: update(current.page.rows),
|
|
158
|
+
total: Math.max(current.page.total + totalDelta, 0),
|
|
159
|
+
};
|
|
160
|
+
cache.current.clear();
|
|
161
|
+
PsServerRowsService.remember(cache.current, current.key, page);
|
|
162
|
+
return { ...current, page };
|
|
163
|
+
});
|
|
164
|
+
}, []);
|
|
165
|
+
const isCurrent = entry !== null &&
|
|
166
|
+
entry.queryKey === queryKey &&
|
|
167
|
+
entry.key === PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
168
|
+
const baseRows = useMemo(() => (entry === null ? [] : entry.page.rows), [entry]);
|
|
169
|
+
const isSameQuery = entry !== null && entry.queryKey === queryKey;
|
|
170
|
+
const rows = useMemo(() => isSameQuery ? baseRows : PsServerRowsService.preview(baseRows, columns, query, getSearchText), [baseRows, columns, getSearchText, isSameQuery, query]);
|
|
171
|
+
const total = entry === null ? 0 : entry.page.total;
|
|
172
|
+
const totalIsEstimate = entry === null ? false : entry.page.totalIsEstimate;
|
|
173
|
+
const isPending = isLoading || query.search !== search || !isCurrent;
|
|
174
|
+
const cursorPaging = useMemo(() => ({
|
|
175
|
+
hasNext: nextCursor !== null,
|
|
176
|
+
hasPrevious: previousCursor !== null,
|
|
177
|
+
isEstimate: totalIsEstimate,
|
|
178
|
+
isPending,
|
|
179
|
+
onNext: goNext,
|
|
180
|
+
onNextIntent: prefetch,
|
|
181
|
+
onPrevious: goPrevious,
|
|
182
|
+
pageNumber: navigation.pageNumber,
|
|
183
|
+
}), [
|
|
184
|
+
goNext,
|
|
185
|
+
goPrevious,
|
|
186
|
+
isPending,
|
|
187
|
+
navigation.pageNumber,
|
|
188
|
+
nextCursor,
|
|
189
|
+
prefetch,
|
|
190
|
+
previousCursor,
|
|
191
|
+
totalIsEstimate,
|
|
192
|
+
]);
|
|
193
|
+
return {
|
|
194
|
+
cursorPaging,
|
|
195
|
+
error,
|
|
196
|
+
hasFailed: error !== null,
|
|
197
|
+
isPending,
|
|
198
|
+
onQueryChange,
|
|
199
|
+
pageCount: PsServerRowsService.pageCount(total, pageSize),
|
|
200
|
+
pageNumber: navigation.pageNumber,
|
|
201
|
+
query,
|
|
202
|
+
reload,
|
|
203
|
+
retry,
|
|
204
|
+
rows,
|
|
205
|
+
total,
|
|
206
|
+
totalIsEstimate,
|
|
207
|
+
updateRows,
|
|
208
|
+
};
|
|
209
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -47,5 +47,16 @@ export * from './interfaces/table-reorder-nesting';
|
|
|
47
47
|
export * from './interfaces/table-nesting-announcements';
|
|
48
48
|
export * from './constants/ps-table-drop-placement';
|
|
49
49
|
export * from './services/ps-table-reorder-service';
|
|
50
|
+
export * from './constants/ps-table-pagination-config';
|
|
50
51
|
export * from './constants/ps-table-pagination-position';
|
|
51
52
|
export * from './interfaces/table-drag-size';
|
|
53
|
+
export * from './components/ps-select-all-matching-banner';
|
|
54
|
+
export * from './constants/ps-server-rows-config';
|
|
55
|
+
export * from './hooks/use-ps-server-rows';
|
|
56
|
+
export * from './interfaces/ps-server-entry';
|
|
57
|
+
export * from './interfaces/ps-server-page';
|
|
58
|
+
export * from './interfaces/ps-server-rows-input';
|
|
59
|
+
export * from './interfaces/ps-server-rows-result';
|
|
60
|
+
export * from './interfaces/ps-table-cursor-paging';
|
|
61
|
+
export * from './interfaces/ps-table-select-all-matching';
|
|
62
|
+
export * from './services/ps-server-rows-service';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PsServerRowsConfig } from '../constants/ps-server-rows-config';
|
|
2
|
+
import { PsTableQueryService } from './ps-table-query-service';
|
|
3
|
+
export class PsServerRowsService {
|
|
4
|
+
static queryKey(query, search, pageSize) {
|
|
5
|
+
const filters = Object.entries(query.filters)
|
|
6
|
+
.filter(([, value]) => PsTableQueryService.isActive(value))
|
|
7
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
8
|
+
return JSON.stringify([filters, search.trim(), query.sort, pageSize]);
|
|
9
|
+
}
|
|
10
|
+
static cacheKey(queryKey, cursor) {
|
|
11
|
+
return `${queryKey}|${cursor === null ? '' : cursor}`;
|
|
12
|
+
}
|
|
13
|
+
static remember(cache, key, page) {
|
|
14
|
+
cache.delete(key);
|
|
15
|
+
cache.set(key, page);
|
|
16
|
+
for (const oldest of cache.keys()) {
|
|
17
|
+
if (cache.size <= PsServerRowsConfig.cacheSize)
|
|
18
|
+
return;
|
|
19
|
+
cache.delete(oldest);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static recall(cache, key) {
|
|
23
|
+
const page = cache.get(key);
|
|
24
|
+
if (page === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
cache.delete(key);
|
|
27
|
+
cache.set(key, page);
|
|
28
|
+
return page;
|
|
29
|
+
}
|
|
30
|
+
static preview(rows, columns, query, getSearchText) {
|
|
31
|
+
return PsTableQueryService.apply(rows, columns, query.filters, query.search, getSearchText, query.sort);
|
|
32
|
+
}
|
|
33
|
+
static pageCount(total, pageSize) {
|
|
34
|
+
return Math.max(Math.ceil(total / pageSize), 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PsServerRowsInput } from '../interfaces/ps-server-rows-input';
|
|
2
|
+
import type { PsServerRowsResult } from '../interfaces/ps-server-rows-result';
|
|
3
|
+
export declare const usePsServerRows: <RowData>({ columns, debounceMs, getSearchText, initialPage, initialQuery, isPrefetching, load, pageSize, }: PsServerRowsInput<RowData>) => PsServerRowsResult<RowData>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsServerRows = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const ps_server_rows_config_1 = require("../constants/ps-server-rows-config");
|
|
6
|
+
const ps_server_rows_service_1 = require("../services/ps-server-rows-service");
|
|
7
|
+
const scheduleIdle = (run) => {
|
|
8
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
9
|
+
const handle = window.requestIdleCallback(run, { timeout: ps_server_rows_config_1.PsServerRowsConfig.idleTimeoutMs });
|
|
10
|
+
return () => window.cancelIdleCallback(handle);
|
|
11
|
+
}
|
|
12
|
+
const timer = window.setTimeout(run, ps_server_rows_config_1.PsServerRowsConfig.idleFallbackMs);
|
|
13
|
+
return () => window.clearTimeout(timer);
|
|
14
|
+
};
|
|
15
|
+
const usePsServerRows = ({ columns, debounceMs = ps_server_rows_config_1.PsServerRowsConfig.debounceMs, getSearchText, initialPage = null, initialQuery, isPrefetching = true, load, pageSize, }) => {
|
|
16
|
+
const [query, setQuery] = (0, react_1.useState)(initialQuery);
|
|
17
|
+
const [search, setSearch] = (0, react_1.useState)(initialQuery.search);
|
|
18
|
+
const [reloadToken, setReloadToken] = (0, react_1.useState)(0);
|
|
19
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(initialPage === null);
|
|
20
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
21
|
+
const queryKey = ps_server_rows_service_1.PsServerRowsService.queryKey(query, search, pageSize);
|
|
22
|
+
const [navigation, setNavigation] = (0, react_1.useState)({
|
|
23
|
+
cursor: null,
|
|
24
|
+
pageNumber: 1,
|
|
25
|
+
queryKey,
|
|
26
|
+
});
|
|
27
|
+
const [entry, setEntry] = (0, react_1.useState)(() => initialPage === null
|
|
28
|
+
? null
|
|
29
|
+
: {
|
|
30
|
+
key: ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, null),
|
|
31
|
+
page: initialPage,
|
|
32
|
+
queryKey,
|
|
33
|
+
});
|
|
34
|
+
const cache = (0, react_1.useRef)(new Map());
|
|
35
|
+
const inflight = (0, react_1.useRef)(new Map());
|
|
36
|
+
const loadRef = (0, react_1.useRef)(load);
|
|
37
|
+
const limitRef = (0, react_1.useRef)(pageSize);
|
|
38
|
+
const queryRef = (0, react_1.useRef)(query);
|
|
39
|
+
const searchRef = (0, react_1.useRef)(search);
|
|
40
|
+
const isPrimed = (0, react_1.useRef)(false);
|
|
41
|
+
loadRef.current = load;
|
|
42
|
+
limitRef.current = pageSize;
|
|
43
|
+
queryRef.current = query;
|
|
44
|
+
searchRef.current = search;
|
|
45
|
+
if (initialPage !== null && !isPrimed.current) {
|
|
46
|
+
isPrimed.current = true;
|
|
47
|
+
ps_server_rows_service_1.PsServerRowsService.remember(cache.current, ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, null), initialPage);
|
|
48
|
+
}
|
|
49
|
+
if (navigation.queryKey !== queryKey)
|
|
50
|
+
setNavigation({ cursor: null, pageNumber: 1, queryKey });
|
|
51
|
+
(0, react_1.useEffect)(() => {
|
|
52
|
+
if (query.search === search)
|
|
53
|
+
return;
|
|
54
|
+
const timer = window.setTimeout(() => setSearch(query.search), debounceMs);
|
|
55
|
+
return () => window.clearTimeout(timer);
|
|
56
|
+
}, [debounceMs, query.search, search]);
|
|
57
|
+
const fetchPage = (0, react_1.useCallback)((cursor, signal, key) => {
|
|
58
|
+
const pending = inflight.current.get(key);
|
|
59
|
+
if (pending !== undefined)
|
|
60
|
+
return pending;
|
|
61
|
+
const request = loadRef.current({ ...queryRef.current, search: searchRef.current }, cursor, signal, limitRef.current);
|
|
62
|
+
const tracked = request.then((page) => {
|
|
63
|
+
inflight.current.delete(key);
|
|
64
|
+
if (!signal.aborted)
|
|
65
|
+
ps_server_rows_service_1.PsServerRowsService.remember(cache.current, key, page);
|
|
66
|
+
return page;
|
|
67
|
+
}, (failure) => {
|
|
68
|
+
inflight.current.delete(key);
|
|
69
|
+
throw failure;
|
|
70
|
+
});
|
|
71
|
+
inflight.current.set(key, tracked);
|
|
72
|
+
return tracked;
|
|
73
|
+
}, []);
|
|
74
|
+
const generation = (0, react_1.useRef)(null);
|
|
75
|
+
(0, react_1.useEffect)(() => {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
generation.current = controller;
|
|
78
|
+
return () => {
|
|
79
|
+
controller.abort();
|
|
80
|
+
inflight.current.clear();
|
|
81
|
+
};
|
|
82
|
+
}, [queryKey, reloadToken]);
|
|
83
|
+
(0, react_1.useEffect)(() => {
|
|
84
|
+
const controller = generation.current;
|
|
85
|
+
if (controller === null)
|
|
86
|
+
return;
|
|
87
|
+
const key = ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
88
|
+
const cached = ps_server_rows_service_1.PsServerRowsService.recall(cache.current, key);
|
|
89
|
+
let isStale = false;
|
|
90
|
+
if (cached !== undefined) {
|
|
91
|
+
setEntry({ key, page: cached, queryKey });
|
|
92
|
+
setIsLoading(false);
|
|
93
|
+
setError(null);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
setIsLoading(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
fetchPage(navigation.cursor, controller.signal, key).then((page) => {
|
|
99
|
+
if (isStale || controller.signal.aborted)
|
|
100
|
+
return;
|
|
101
|
+
setEntry({ key, page, queryKey });
|
|
102
|
+
setIsLoading(false);
|
|
103
|
+
}, (failure) => {
|
|
104
|
+
if (isStale || controller.signal.aborted)
|
|
105
|
+
return;
|
|
106
|
+
setError(failure);
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
});
|
|
109
|
+
return () => {
|
|
110
|
+
isStale = true;
|
|
111
|
+
};
|
|
112
|
+
}, [fetchPage, navigation.cursor, queryKey, reloadToken]);
|
|
113
|
+
const nextCursor = entry !== null && entry.queryKey === queryKey ? entry.page.nextCursor : null;
|
|
114
|
+
const prefetch = (0, react_1.useCallback)(() => {
|
|
115
|
+
const controller = generation.current;
|
|
116
|
+
if (controller === null || controller.signal.aborted || nextCursor === null)
|
|
117
|
+
return;
|
|
118
|
+
const key = ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, nextCursor);
|
|
119
|
+
if (cache.current.has(key) || inflight.current.has(key))
|
|
120
|
+
return;
|
|
121
|
+
fetchPage(nextCursor, controller.signal, key).catch(() => undefined);
|
|
122
|
+
}, [fetchPage, nextCursor, queryKey]);
|
|
123
|
+
(0, react_1.useEffect)(() => {
|
|
124
|
+
if (!isPrefetching || isLoading || nextCursor === null)
|
|
125
|
+
return;
|
|
126
|
+
return scheduleIdle(prefetch);
|
|
127
|
+
}, [isLoading, isPrefetching, nextCursor, prefetch]);
|
|
128
|
+
const onQueryChange = (0, react_1.useCallback)((next) => setQuery(next), []);
|
|
129
|
+
const goNext = (0, react_1.useCallback)(() => {
|
|
130
|
+
if (nextCursor === null)
|
|
131
|
+
return;
|
|
132
|
+
setNavigation((current) => ({
|
|
133
|
+
...current,
|
|
134
|
+
cursor: nextCursor,
|
|
135
|
+
pageNumber: current.pageNumber + 1,
|
|
136
|
+
}));
|
|
137
|
+
}, [nextCursor]);
|
|
138
|
+
const previousCursor = entry !== null && entry.queryKey === queryKey ? entry.page.prevCursor : null;
|
|
139
|
+
const goPrevious = (0, react_1.useCallback)(() => {
|
|
140
|
+
if (previousCursor === null)
|
|
141
|
+
return;
|
|
142
|
+
setNavigation((current) => ({
|
|
143
|
+
...current,
|
|
144
|
+
cursor: previousCursor,
|
|
145
|
+
pageNumber: Math.max(current.pageNumber - 1, 1),
|
|
146
|
+
}));
|
|
147
|
+
}, [previousCursor]);
|
|
148
|
+
const reload = (0, react_1.useCallback)(() => {
|
|
149
|
+
cache.current.clear();
|
|
150
|
+
setNavigation((current) => ({ ...current, cursor: null, pageNumber: 1 }));
|
|
151
|
+
setReloadToken((current) => current + 1);
|
|
152
|
+
}, []);
|
|
153
|
+
const retry = (0, react_1.useCallback)(() => setReloadToken((current) => current + 1), []);
|
|
154
|
+
const updateRows = (0, react_1.useCallback)((update, totalDelta = 0) => {
|
|
155
|
+
setEntry((current) => {
|
|
156
|
+
if (current === null)
|
|
157
|
+
return current;
|
|
158
|
+
const page = {
|
|
159
|
+
...current.page,
|
|
160
|
+
rows: update(current.page.rows),
|
|
161
|
+
total: Math.max(current.page.total + totalDelta, 0),
|
|
162
|
+
};
|
|
163
|
+
cache.current.clear();
|
|
164
|
+
ps_server_rows_service_1.PsServerRowsService.remember(cache.current, current.key, page);
|
|
165
|
+
return { ...current, page };
|
|
166
|
+
});
|
|
167
|
+
}, []);
|
|
168
|
+
const isCurrent = entry !== null &&
|
|
169
|
+
entry.queryKey === queryKey &&
|
|
170
|
+
entry.key === ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
171
|
+
const baseRows = (0, react_1.useMemo)(() => (entry === null ? [] : entry.page.rows), [entry]);
|
|
172
|
+
const isSameQuery = entry !== null && entry.queryKey === queryKey;
|
|
173
|
+
const rows = (0, react_1.useMemo)(() => isSameQuery ? baseRows : ps_server_rows_service_1.PsServerRowsService.preview(baseRows, columns, query, getSearchText), [baseRows, columns, getSearchText, isSameQuery, query]);
|
|
174
|
+
const total = entry === null ? 0 : entry.page.total;
|
|
175
|
+
const totalIsEstimate = entry === null ? false : entry.page.totalIsEstimate;
|
|
176
|
+
const isPending = isLoading || query.search !== search || !isCurrent;
|
|
177
|
+
const cursorPaging = (0, react_1.useMemo)(() => ({
|
|
178
|
+
hasNext: nextCursor !== null,
|
|
179
|
+
hasPrevious: previousCursor !== null,
|
|
180
|
+
isEstimate: totalIsEstimate,
|
|
181
|
+
isPending,
|
|
182
|
+
onNext: goNext,
|
|
183
|
+
onNextIntent: prefetch,
|
|
184
|
+
onPrevious: goPrevious,
|
|
185
|
+
pageNumber: navigation.pageNumber,
|
|
186
|
+
}), [
|
|
187
|
+
goNext,
|
|
188
|
+
goPrevious,
|
|
189
|
+
isPending,
|
|
190
|
+
navigation.pageNumber,
|
|
191
|
+
nextCursor,
|
|
192
|
+
prefetch,
|
|
193
|
+
previousCursor,
|
|
194
|
+
totalIsEstimate,
|
|
195
|
+
]);
|
|
196
|
+
return {
|
|
197
|
+
cursorPaging,
|
|
198
|
+
error,
|
|
199
|
+
hasFailed: error !== null,
|
|
200
|
+
isPending,
|
|
201
|
+
onQueryChange,
|
|
202
|
+
pageCount: ps_server_rows_service_1.PsServerRowsService.pageCount(total, pageSize),
|
|
203
|
+
pageNumber: navigation.pageNumber,
|
|
204
|
+
query,
|
|
205
|
+
reload,
|
|
206
|
+
retry,
|
|
207
|
+
rows,
|
|
208
|
+
total,
|
|
209
|
+
totalIsEstimate,
|
|
210
|
+
updateRows,
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
exports.usePsServerRows = usePsServerRows;
|
package/dist/index.d.ts
CHANGED
|
@@ -47,5 +47,16 @@ export * from './interfaces/table-reorder-nesting';
|
|
|
47
47
|
export * from './interfaces/table-nesting-announcements';
|
|
48
48
|
export * from './constants/ps-table-drop-placement';
|
|
49
49
|
export * from './services/ps-table-reorder-service';
|
|
50
|
+
export * from './constants/ps-table-pagination-config';
|
|
50
51
|
export * from './constants/ps-table-pagination-position';
|
|
51
52
|
export * from './interfaces/table-drag-size';
|
|
53
|
+
export * from './components/ps-select-all-matching-banner';
|
|
54
|
+
export * from './constants/ps-server-rows-config';
|
|
55
|
+
export * from './hooks/use-ps-server-rows';
|
|
56
|
+
export * from './interfaces/ps-server-entry';
|
|
57
|
+
export * from './interfaces/ps-server-page';
|
|
58
|
+
export * from './interfaces/ps-server-rows-input';
|
|
59
|
+
export * from './interfaces/ps-server-rows-result';
|
|
60
|
+
export * from './interfaces/ps-table-cursor-paging';
|
|
61
|
+
export * from './interfaces/ps-table-select-all-matching';
|
|
62
|
+
export * from './services/ps-server-rows-service';
|
package/dist/index.js
CHANGED
|
@@ -63,5 +63,16 @@ __exportStar(require("./interfaces/table-reorder-nesting"), exports);
|
|
|
63
63
|
__exportStar(require("./interfaces/table-nesting-announcements"), exports);
|
|
64
64
|
__exportStar(require("./constants/ps-table-drop-placement"), exports);
|
|
65
65
|
__exportStar(require("./services/ps-table-reorder-service"), exports);
|
|
66
|
+
__exportStar(require("./constants/ps-table-pagination-config"), exports);
|
|
66
67
|
__exportStar(require("./constants/ps-table-pagination-position"), exports);
|
|
67
68
|
__exportStar(require("./interfaces/table-drag-size"), exports);
|
|
69
|
+
__exportStar(require("./components/ps-select-all-matching-banner"), exports);
|
|
70
|
+
__exportStar(require("./constants/ps-server-rows-config"), exports);
|
|
71
|
+
__exportStar(require("./hooks/use-ps-server-rows"), exports);
|
|
72
|
+
__exportStar(require("./interfaces/ps-server-entry"), exports);
|
|
73
|
+
__exportStar(require("./interfaces/ps-server-page"), exports);
|
|
74
|
+
__exportStar(require("./interfaces/ps-server-rows-input"), exports);
|
|
75
|
+
__exportStar(require("./interfaces/ps-server-rows-result"), exports);
|
|
76
|
+
__exportStar(require("./interfaces/ps-table-cursor-paging"), exports);
|
|
77
|
+
__exportStar(require("./interfaces/ps-table-select-all-matching"), exports);
|
|
78
|
+
__exportStar(require("./services/ps-server-rows-service"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PsServerPage } from './ps-server-page';
|
|
2
|
+
import type { PsTableColumn } from './table-column';
|
|
3
|
+
import type { PsTableQuery } from './table-query';
|
|
4
|
+
export interface PsServerRowsInput<RowData> {
|
|
5
|
+
columns: PsTableColumn<RowData>[];
|
|
6
|
+
debounceMs?: number;
|
|
7
|
+
getSearchText?: (row: RowData) => string;
|
|
8
|
+
initialPage?: PsServerPage<RowData> | null;
|
|
9
|
+
initialQuery: PsTableQuery;
|
|
10
|
+
isPrefetching?: boolean;
|
|
11
|
+
load: (query: PsTableQuery, cursor: string | null, signal: AbortSignal, limit: number) => Promise<PsServerPage<RowData>>;
|
|
12
|
+
pageSize: number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PsTableCursorPaging } from './ps-table-cursor-paging';
|
|
2
|
+
import type { PsTableQuery } from './table-query';
|
|
3
|
+
export interface PsServerRowsResult<RowData> {
|
|
4
|
+
cursorPaging: PsTableCursorPaging;
|
|
5
|
+
error: unknown;
|
|
6
|
+
hasFailed: boolean;
|
|
7
|
+
isPending: boolean;
|
|
8
|
+
onQueryChange: (query: PsTableQuery) => void;
|
|
9
|
+
pageCount: number;
|
|
10
|
+
pageNumber: number;
|
|
11
|
+
query: PsTableQuery;
|
|
12
|
+
reload: () => void;
|
|
13
|
+
retry: () => void;
|
|
14
|
+
rows: RowData[];
|
|
15
|
+
total: number;
|
|
16
|
+
totalIsEstimate: boolean;
|
|
17
|
+
updateRows: (update: (rows: RowData[]) => RowData[], totalDelta?: number) => void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PsTableCursorPaging {
|
|
2
|
+
hasNext: boolean;
|
|
3
|
+
hasPrevious: boolean;
|
|
4
|
+
isEstimate: boolean;
|
|
5
|
+
isPending?: boolean;
|
|
6
|
+
onNext: () => void;
|
|
7
|
+
onNextIntent?: () => void;
|
|
8
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
9
|
+
onPrevious: () => void;
|
|
10
|
+
pageNumber: number;
|
|
11
|
+
pageSizeOptions?: number[];
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PsServerPage } from '../interfaces/ps-server-page';
|
|
2
|
+
import type { PsTableColumn } from '../interfaces/table-column';
|
|
3
|
+
import type { PsTableQuery } from '../interfaces/table-query';
|
|
4
|
+
export declare class PsServerRowsService {
|
|
5
|
+
static queryKey(query: PsTableQuery, search: string, pageSize: number): string;
|
|
6
|
+
static cacheKey(queryKey: string, cursor: string | null): string;
|
|
7
|
+
static remember<RowData>(cache: Map<string, PsServerPage<RowData>>, key: string, page: PsServerPage<RowData>): void;
|
|
8
|
+
static recall<RowData>(cache: Map<string, PsServerPage<RowData>>, key: string): PsServerPage<RowData> | undefined;
|
|
9
|
+
static preview<RowData>(rows: RowData[], columns: PsTableColumn<RowData>[], query: PsTableQuery, getSearchText: ((row: RowData) => string) | undefined): RowData[];
|
|
10
|
+
static pageCount(total: number, pageSize: number): number;
|
|
11
|
+
}
|