@backstage/ui 0.11.0-next.0 → 0.11.0-next.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/CHANGELOG.md +63 -0
- package/css/styles.css +36 -12
- package/dist/components/Box/Box.esm.js +13 -6
- package/dist/components/Box/Box.esm.js.map +1 -1
- package/dist/components/Box/Box.module.css.esm.js +1 -1
- package/dist/components/Box/definition.esm.js +4 -1
- package/dist/components/Box/definition.esm.js.map +1 -1
- package/dist/components/Button/Button.esm.js +12 -1
- package/dist/components/Button/Button.esm.js.map +1 -1
- package/dist/components/Button/Button.module.css.esm.js +1 -1
- package/dist/components/Flex/Flex.esm.js +14 -6
- package/dist/components/Flex/Flex.esm.js.map +1 -1
- package/dist/components/Flex/Flex.module.css.esm.js +1 -1
- package/dist/components/Flex/definition.esm.js +4 -1
- package/dist/components/Flex/definition.esm.js.map +1 -1
- package/dist/components/Grid/Grid.esm.js +27 -12
- package/dist/components/Grid/Grid.esm.js.map +1 -1
- package/dist/components/Grid/Grid.module.css.esm.js +2 -2
- package/dist/components/Grid/definition.esm.js +8 -2
- package/dist/components/Grid/definition.esm.js.map +1 -1
- package/dist/components/Link/Link.esm.js +9 -6
- package/dist/components/Link/Link.esm.js.map +1 -1
- package/dist/components/Table/Table.module.css.esm.js +1 -1
- package/dist/components/Table/components/Table.esm.js +154 -15
- package/dist/components/Table/components/Table.esm.js.map +1 -1
- package/dist/components/Table/components/TableRoot.esm.js +26 -0
- package/dist/components/Table/components/TableRoot.esm.js.map +1 -0
- package/dist/components/Table/definition.esm.js +3 -0
- package/dist/components/Table/definition.esm.js.map +1 -1
- package/dist/components/Table/hooks/useCompletePagination.esm.js +106 -0
- package/dist/components/Table/hooks/useCompletePagination.esm.js.map +1 -0
- package/dist/components/Table/hooks/useCursorPagination.esm.js +58 -0
- package/dist/components/Table/hooks/useCursorPagination.esm.js.map +1 -0
- package/dist/components/Table/hooks/useDebouncedReload.esm.js +17 -0
- package/dist/components/Table/hooks/useDebouncedReload.esm.js.map +1 -0
- package/dist/components/Table/hooks/useOffsetPagination.esm.js +64 -0
- package/dist/components/Table/hooks/useOffsetPagination.esm.js.map +1 -0
- package/dist/components/Table/hooks/usePageCache.esm.js +168 -0
- package/dist/components/Table/hooks/usePageCache.esm.js.map +1 -0
- package/dist/components/Table/hooks/useQueryState.esm.js +42 -0
- package/dist/components/Table/hooks/useQueryState.esm.js.map +1 -0
- package/dist/components/Table/hooks/useStableCallback.esm.js +10 -0
- package/dist/components/Table/hooks/useStableCallback.esm.js.map +1 -0
- package/dist/components/Table/hooks/useTable.esm.js +80 -99
- package/dist/components/Table/hooks/useTable.esm.js.map +1 -1
- package/dist/components/TablePagination/TablePagination.esm.js +76 -101
- package/dist/components/TablePagination/TablePagination.esm.js.map +1 -1
- package/dist/components/ToggleButton/ToggleButton.esm.js +51 -0
- package/dist/components/ToggleButton/ToggleButton.esm.js.map +1 -0
- package/dist/components/ToggleButton/ToggleButton.module.css.esm.js +8 -0
- package/dist/components/ToggleButton/ToggleButton.module.css.esm.js.map +1 -0
- package/dist/components/ToggleButton/definition.esm.js +12 -0
- package/dist/components/ToggleButton/definition.esm.js.map +1 -0
- package/dist/components/ToggleButtonGroup/ToggleButtonGroup.esm.js +33 -0
- package/dist/components/ToggleButtonGroup/ToggleButtonGroup.esm.js.map +1 -0
- package/dist/components/ToggleButtonGroup/ToggleButtonGroup.module.css.esm.js +8 -0
- package/dist/components/ToggleButtonGroup/ToggleButtonGroup.module.css.esm.js.map +1 -0
- package/dist/components/ToggleButtonGroup/definition.esm.js +11 -0
- package/dist/components/ToggleButtonGroup/definition.esm.js.map +1 -0
- package/dist/hooks/useSurface.esm.js +74 -0
- package/dist/hooks/useSurface.esm.js.map +1 -0
- package/dist/index.d.ts +306 -92
- package/dist/index.esm.js +5 -0
- package/dist/index.esm.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOffsetPagination.esm.js","sources":["../../../../src/components/Table/hooks/useOffsetPagination.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState, useCallback } from 'react';\nimport type { TableItem } from '../types';\nimport type {\n UseTableOffsetOptions,\n OffsetParams,\n QueryState,\n PaginationResult,\n} from './types';\nimport { usePageCache } from './usePageCache';\nimport { useStableCallback } from './useStableCallback';\nimport { useDebouncedReload } from './useDebouncedReload';\n\nexport function useOffsetPagination<T extends TableItem, TFilter>(\n options: UseTableOffsetOptions<T, TFilter>,\n query: QueryState<TFilter>,\n): PaginationResult<T> & { reload: () => void } {\n const { getData: getDataProp, paginationOptions = {} } = options;\n const { pageSize: defaultPageSize = 20, initialOffset = 0 } =\n paginationOptions;\n\n const getData = useStableCallback(getDataProp);\n const { sort, filter, search } = query;\n\n const [pageSize, setPageSize] = useState(defaultPageSize);\n\n const wrappedGetData = useCallback(\n async ({\n cursor,\n signal,\n }: {\n cursor: number | undefined;\n signal: AbortSignal;\n }) => {\n const currentOffset = cursor ?? 0;\n\n const params: OffsetParams<TFilter> = {\n offset: currentOffset,\n pageSize,\n sort,\n filter,\n search,\n signal,\n };\n\n const response = await getData(params);\n\n const prevCursor =\n currentOffset > 0 ? Math.max(0, currentOffset - pageSize) : undefined;\n const nextCursor =\n currentOffset + pageSize < response.totalCount\n ? currentOffset + pageSize\n : undefined;\n\n return {\n data: response.data,\n prevCursor,\n nextCursor,\n totalCount: response.totalCount,\n };\n },\n [getData, pageSize, sort, filter, search],\n );\n\n const cache = usePageCache<T, number>({\n getData: wrappedGetData,\n initialCurrentCursor: initialOffset > 0 ? initialOffset : undefined,\n });\n\n useDebouncedReload(query, pageSize, cache.reload);\n\n const onPageSizeChange = useCallback(\n (newSize: number) => setPageSize(newSize),\n [],\n );\n\n return {\n data: cache.data,\n loading: cache.loading,\n error: cache.error,\n totalCount: cache.totalCount,\n offset: cache.currentCursor ?? 0,\n pageSize,\n hasNextPage: cache.hasNextPage,\n hasPreviousPage: cache.hasPreviousPage,\n onNextPage: cache.onNextPage,\n onPreviousPage: cache.onPreviousPage,\n onPageSizeChange,\n reload: cache.reload,\n };\n}\n"],"names":[],"mappings":";;;;;AA2BO,SAAS,mBAAA,CACd,SACA,KAAA,EAC8C;AAC9C,EAAA,MAAM,EAAE,OAAA,EAAS,WAAA,EAAa,iBAAA,GAAoB,IAAG,GAAI,OAAA;AACzD,EAAA,MAAM,EAAE,QAAA,EAAU,eAAA,GAAkB,EAAA,EAAI,aAAA,GAAgB,GAAE,GACxD,iBAAA;AAEF,EAAA,MAAM,OAAA,GAAU,kBAAkB,WAAW,CAAA;AAC7C,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAO,GAAI,KAAA;AAEjC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,eAAe,CAAA;AAExD,EAAA,MAAM,cAAA,GAAiB,WAAA;AAAA,IACrB,OAAO;AAAA,MACL,MAAA;AAAA,MACA;AAAA,KACF,KAGM;AACJ,MAAA,MAAM,gBAAgB,MAAA,IAAU,CAAA;AAEhC,MAAA,MAAM,MAAA,GAAgC;AAAA,QACpC,MAAA,EAAQ,aAAA;AAAA,QACR,QAAA;AAAA,QACA,IAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAEA,MAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,MAAM,CAAA;AAErC,MAAA,MAAM,UAAA,GACJ,gBAAgB,CAAA,GAAI,IAAA,CAAK,IAAI,CAAA,EAAG,aAAA,GAAgB,QAAQ,CAAA,GAAI,MAAA;AAC9D,MAAA,MAAM,aACJ,aAAA,GAAgB,QAAA,GAAW,QAAA,CAAS,UAAA,GAChC,gBAAgB,QAAA,GAChB,MAAA;AAEN,MAAA,OAAO;AAAA,QACL,MAAM,QAAA,CAAS,IAAA;AAAA,QACf,UAAA;AAAA,QACA,UAAA;AAAA,QACA,YAAY,QAAA,CAAS;AAAA,OACvB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,EAAM,QAAQ,MAAM;AAAA,GAC1C;AAEA,EAAA,MAAM,QAAQ,YAAA,CAAwB;AAAA,IACpC,OAAA,EAAS,cAAA;AAAA,IACT,oBAAA,EAAsB,aAAA,GAAgB,CAAA,GAAI,aAAA,GAAgB;AAAA,GAC3D,CAAA;AAED,EAAA,kBAAA,CAAmB,KAAA,EAAO,QAAA,EAAU,KAAA,CAAM,MAAM,CAAA;AAEhD,EAAA,MAAM,gBAAA,GAAmB,WAAA;AAAA,IACvB,CAAC,OAAA,KAAoB,WAAA,CAAY,OAAO,CAAA;AAAA,IACxC;AAAC,GACH;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,SAAS,KAAA,CAAM,OAAA;AAAA,IACf,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,MAAA,EAAQ,MAAM,aAAA,IAAiB,CAAA;AAAA,IAC/B,QAAA;AAAA,IACA,aAAa,KAAA,CAAM,WAAA;AAAA,IACnB,iBAAiB,KAAA,CAAM,eAAA;AAAA,IACvB,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,gBAAgB,KAAA,CAAM,cAAA;AAAA,IACtB,gBAAA;AAAA,IACA,QAAQ,KAAA,CAAM;AAAA,GAChB;AACF;;;;"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const FIRST_PAGE_CURSOR = /* @__PURE__ */ Symbol("firstPage");
|
|
4
|
+
class PageCacheStore {
|
|
5
|
+
cache = /* @__PURE__ */ new Map();
|
|
6
|
+
get(cursor) {
|
|
7
|
+
return this.cache.get(cursor);
|
|
8
|
+
}
|
|
9
|
+
getOrCreate(cursor) {
|
|
10
|
+
const existing = this.cache.get(cursor);
|
|
11
|
+
if (existing) {
|
|
12
|
+
return existing;
|
|
13
|
+
}
|
|
14
|
+
const entry = {
|
|
15
|
+
data: void 0,
|
|
16
|
+
nextCursor: void 0,
|
|
17
|
+
prevCursor: void 0
|
|
18
|
+
};
|
|
19
|
+
this.cache.set(cursor, entry);
|
|
20
|
+
return entry;
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.cache.clear();
|
|
24
|
+
}
|
|
25
|
+
getTargetCursor(direction, currentCursor, initialCurrentCursor) {
|
|
26
|
+
if (direction === "mount") {
|
|
27
|
+
return toInternalCursor(initialCurrentCursor);
|
|
28
|
+
}
|
|
29
|
+
if (direction === "reset") {
|
|
30
|
+
return FIRST_PAGE_CURSOR;
|
|
31
|
+
}
|
|
32
|
+
if (direction === "refresh") {
|
|
33
|
+
return currentCursor;
|
|
34
|
+
}
|
|
35
|
+
const currentEntry = this.cache.get(currentCursor);
|
|
36
|
+
if (!currentEntry) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
return direction === "next" ? currentEntry.nextCursor : currentEntry.prevCursor;
|
|
40
|
+
}
|
|
41
|
+
linkEntryToSource(entry, direction, currentCursor) {
|
|
42
|
+
if (direction === "next") {
|
|
43
|
+
entry.prevCursor = currentCursor;
|
|
44
|
+
} else if (direction === "prev") {
|
|
45
|
+
entry.nextCursor = currentCursor;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function toInternalCursor(cursor) {
|
|
50
|
+
return cursor === void 0 ? FIRST_PAGE_CURSOR : cursor;
|
|
51
|
+
}
|
|
52
|
+
function toExternalCursor(cursor) {
|
|
53
|
+
return cursor === FIRST_PAGE_CURSOR ? void 0 : cursor;
|
|
54
|
+
}
|
|
55
|
+
function usePageCache(options) {
|
|
56
|
+
const { getData, initialCurrentCursor } = options;
|
|
57
|
+
const [currentCursor, setCurrentCursor] = useState(
|
|
58
|
+
() => toInternalCursor(initialCurrentCursor)
|
|
59
|
+
);
|
|
60
|
+
const cacheStore = useRef(new PageCacheStore()).current;
|
|
61
|
+
const [loading, setLoading] = useState(true);
|
|
62
|
+
const [error, setError] = useState(void 0);
|
|
63
|
+
const [totalCount, setTotalCount] = useState(void 0);
|
|
64
|
+
const abortControllerRef = useRef(null);
|
|
65
|
+
const currentPage = cacheStore.get(currentCursor);
|
|
66
|
+
const data = currentPage?.data;
|
|
67
|
+
const hasNextPage = currentPage?.nextCursor !== void 0;
|
|
68
|
+
const hasPreviousPage = currentPage?.prevCursor !== void 0;
|
|
69
|
+
const goToPage = useCallback(
|
|
70
|
+
async (direction) => {
|
|
71
|
+
const targetCursor = cacheStore.getTargetCursor(
|
|
72
|
+
direction,
|
|
73
|
+
currentCursor,
|
|
74
|
+
initialCurrentCursor
|
|
75
|
+
);
|
|
76
|
+
if (!targetCursor) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const existingEntry = cacheStore.get(targetCursor);
|
|
80
|
+
if (existingEntry?.data !== void 0) {
|
|
81
|
+
setCurrentCursor(targetCursor);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const entry = cacheStore.getOrCreate(targetCursor);
|
|
85
|
+
cacheStore.linkEntryToSource(entry, direction, currentCursor);
|
|
86
|
+
setCurrentCursor(targetCursor);
|
|
87
|
+
if (abortControllerRef.current) {
|
|
88
|
+
abortControllerRef.current.abort();
|
|
89
|
+
}
|
|
90
|
+
const abortController = new AbortController();
|
|
91
|
+
abortControllerRef.current = abortController;
|
|
92
|
+
setLoading(true);
|
|
93
|
+
setError(void 0);
|
|
94
|
+
try {
|
|
95
|
+
const result = await getData({
|
|
96
|
+
cursor: toExternalCursor(targetCursor),
|
|
97
|
+
signal: abortController.signal
|
|
98
|
+
});
|
|
99
|
+
if (abortController.signal.aborted) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
entry.data = result.data;
|
|
103
|
+
if (entry.nextCursor === void 0 && result.nextCursor !== void 0) {
|
|
104
|
+
entry.nextCursor = result.nextCursor;
|
|
105
|
+
}
|
|
106
|
+
if (entry.prevCursor === void 0 && result.prevCursor !== void 0) {
|
|
107
|
+
entry.prevCursor = result.prevCursor;
|
|
108
|
+
}
|
|
109
|
+
if (result.totalCount !== void 0) {
|
|
110
|
+
setTotalCount(result.totalCount);
|
|
111
|
+
}
|
|
112
|
+
setLoading(false);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (abortController.signal.aborted) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
118
|
+
setLoading(false);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
[getData, initialCurrentCursor, currentCursor, cacheStore]
|
|
122
|
+
);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
goToPage("mount");
|
|
125
|
+
return () => {
|
|
126
|
+
if (abortControllerRef.current) {
|
|
127
|
+
abortControllerRef.current.abort();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}, []);
|
|
131
|
+
const onNextPage = useCallback(() => {
|
|
132
|
+
if (loading) return;
|
|
133
|
+
const page = cacheStore.get(currentCursor);
|
|
134
|
+
if (!page?.nextCursor) return;
|
|
135
|
+
goToPage("next");
|
|
136
|
+
}, [loading, currentCursor, goToPage, cacheStore]);
|
|
137
|
+
const onPreviousPage = useCallback(() => {
|
|
138
|
+
if (loading) return;
|
|
139
|
+
const page = cacheStore.get(currentCursor);
|
|
140
|
+
if (!page?.prevCursor) return;
|
|
141
|
+
goToPage("prev");
|
|
142
|
+
}, [loading, currentCursor, goToPage, cacheStore]);
|
|
143
|
+
const reload = useCallback(
|
|
144
|
+
(reloadOptions) => {
|
|
145
|
+
if (abortControllerRef.current) {
|
|
146
|
+
abortControllerRef.current.abort();
|
|
147
|
+
}
|
|
148
|
+
cacheStore.clear();
|
|
149
|
+
goToPage(reloadOptions?.keepCurrentCursor ? "refresh" : "reset");
|
|
150
|
+
},
|
|
151
|
+
[goToPage, cacheStore]
|
|
152
|
+
);
|
|
153
|
+
return {
|
|
154
|
+
loading,
|
|
155
|
+
error,
|
|
156
|
+
data,
|
|
157
|
+
totalCount,
|
|
158
|
+
currentCursor: toExternalCursor(currentCursor),
|
|
159
|
+
hasPreviousPage,
|
|
160
|
+
onPreviousPage,
|
|
161
|
+
hasNextPage,
|
|
162
|
+
onNextPage,
|
|
163
|
+
reload
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export { usePageCache };
|
|
168
|
+
//# sourceMappingURL=usePageCache.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePageCache.esm.js","sources":["../../../../src/components/Table/hooks/usePageCache.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react';\n\nconst FIRST_PAGE_CURSOR = Symbol('firstPage');\n\ntype CursorType = string | number;\n\ntype InternalCursor<TCursor extends CursorType> =\n | TCursor\n | typeof FIRST_PAGE_CURSOR;\n\ninterface PageEntry<T, TCursor extends CursorType> {\n data: T[] | undefined;\n nextCursor: InternalCursor<TCursor> | undefined;\n prevCursor: InternalCursor<TCursor> | undefined;\n}\n\ninterface GetDataResult<T, TCursor extends CursorType> {\n data: T[];\n nextCursor?: TCursor;\n prevCursor?: TCursor;\n totalCount?: number;\n}\n\n/** @internal */\nexport interface UsePageCacheOptions<T, TCursor extends CursorType = string> {\n getData: (params: {\n cursor: TCursor | undefined;\n signal: AbortSignal;\n }) => Promise<GetDataResult<T, TCursor>>;\n initialCurrentCursor?: TCursor;\n}\n\n/** @internal */\nexport interface UsePageCacheResult<T, TCursor extends CursorType = string> {\n loading: boolean;\n error: Error | undefined;\n data: T[] | undefined;\n totalCount: number | undefined;\n currentCursor: TCursor | undefined;\n hasPreviousPage: boolean;\n onPreviousPage: () => void;\n hasNextPage: boolean;\n onNextPage: () => void;\n reload: (options?: { keepCurrentCursor?: boolean }) => void;\n}\n\ntype Direction = 'mount' | 'reset' | 'refresh' | 'next' | 'prev';\n\nclass PageCacheStore<T, TCursor extends CursorType> {\n private cache = new Map<InternalCursor<TCursor>, PageEntry<T, TCursor>>();\n\n get(cursor: InternalCursor<TCursor>): PageEntry<T, TCursor> | undefined {\n return this.cache.get(cursor);\n }\n\n getOrCreate(cursor: InternalCursor<TCursor>): PageEntry<T, TCursor> {\n const existing = this.cache.get(cursor);\n if (existing) {\n return existing;\n }\n const entry: PageEntry<T, TCursor> = {\n data: undefined,\n nextCursor: undefined,\n prevCursor: undefined,\n };\n this.cache.set(cursor, entry);\n return entry;\n }\n\n clear() {\n this.cache.clear();\n }\n\n getTargetCursor(\n direction: Direction,\n currentCursor: InternalCursor<TCursor>,\n initialCurrentCursor: TCursor | undefined,\n ): InternalCursor<TCursor> | undefined {\n if (direction === 'mount') {\n return toInternalCursor(initialCurrentCursor);\n }\n if (direction === 'reset') {\n return FIRST_PAGE_CURSOR;\n }\n if (direction === 'refresh') {\n return currentCursor;\n }\n const currentEntry = this.cache.get(currentCursor);\n\n if (!currentEntry) {\n return;\n }\n\n return direction === 'next'\n ? currentEntry.nextCursor\n : currentEntry.prevCursor;\n }\n\n linkEntryToSource(\n entry: PageEntry<T, TCursor>,\n direction: Direction,\n currentCursor: InternalCursor<TCursor>,\n ) {\n if (direction === 'next') {\n entry.prevCursor = currentCursor;\n } else if (direction === 'prev') {\n entry.nextCursor = currentCursor;\n }\n }\n}\n\nfunction toInternalCursor<TCursor extends CursorType>(\n cursor: TCursor | undefined,\n): InternalCursor<TCursor> {\n return cursor === undefined ? FIRST_PAGE_CURSOR : cursor;\n}\n\nfunction toExternalCursor<TCursor extends CursorType>(\n cursor: InternalCursor<TCursor>,\n): TCursor | undefined {\n return cursor === FIRST_PAGE_CURSOR ? undefined : cursor;\n}\n\n/** @internal */\nexport function usePageCache<T, TCursor extends CursorType = string>(\n options: UsePageCacheOptions<T, TCursor>,\n): UsePageCacheResult<T, TCursor> {\n const { getData, initialCurrentCursor } = options;\n\n const [currentCursor, setCurrentCursor] = useState<InternalCursor<TCursor>>(\n () => toInternalCursor(initialCurrentCursor),\n );\n\n const cacheStore = useRef(new PageCacheStore<T, TCursor>()).current;\n\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [totalCount, setTotalCount] = useState<number | undefined>(undefined);\n\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const currentPage = cacheStore.get(currentCursor);\n const data = currentPage?.data;\n const hasNextPage = currentPage?.nextCursor !== undefined;\n const hasPreviousPage = currentPage?.prevCursor !== undefined;\n\n const goToPage = useCallback(\n async (direction: Direction) => {\n const targetCursor = cacheStore.getTargetCursor(\n direction,\n currentCursor,\n initialCurrentCursor,\n );\n\n if (!targetCursor) {\n return;\n }\n\n const existingEntry = cacheStore.get(targetCursor);\n if (existingEntry?.data !== undefined) {\n setCurrentCursor(targetCursor);\n return;\n }\n\n const entry = cacheStore.getOrCreate(targetCursor);\n cacheStore.linkEntryToSource(entry, direction, currentCursor);\n setCurrentCursor(targetCursor);\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n setLoading(true);\n setError(undefined);\n\n try {\n const result = await getData({\n cursor: toExternalCursor(targetCursor),\n signal: abortController.signal,\n });\n\n if (abortController.signal.aborted) {\n return;\n }\n\n entry.data = result.data;\n\n if (entry.nextCursor === undefined && result.nextCursor !== undefined) {\n entry.nextCursor = result.nextCursor;\n }\n if (entry.prevCursor === undefined && result.prevCursor !== undefined) {\n entry.prevCursor = result.prevCursor;\n }\n\n if (result.totalCount !== undefined) {\n setTotalCount(result.totalCount);\n }\n\n setLoading(false);\n } catch (err) {\n if (abortController.signal.aborted) {\n return;\n }\n\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n },\n [getData, initialCurrentCursor, currentCursor, cacheStore],\n );\n\n useEffect(() => {\n goToPage('mount');\n\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n const onNextPage = useCallback(() => {\n if (loading) return;\n const page = cacheStore.get(currentCursor);\n if (!page?.nextCursor) return;\n goToPage('next');\n }, [loading, currentCursor, goToPage, cacheStore]);\n\n const onPreviousPage = useCallback(() => {\n if (loading) return;\n const page = cacheStore.get(currentCursor);\n if (!page?.prevCursor) return;\n goToPage('prev');\n }, [loading, currentCursor, goToPage, cacheStore]);\n\n const reload = useCallback(\n (reloadOptions?: { keepCurrentCursor?: boolean }) => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n cacheStore.clear();\n\n goToPage(reloadOptions?.keepCurrentCursor ? 'refresh' : 'reset');\n },\n [goToPage, cacheStore],\n );\n\n return {\n loading,\n error,\n data,\n totalCount,\n currentCursor: toExternalCursor(currentCursor),\n hasPreviousPage,\n onPreviousPage,\n hasNextPage,\n onNextPage,\n reload,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo, useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function useControlledStateHelper(initialValue, controlledValue, onChange) {
|
|
4
|
+
const [internalValue, setInternalValue] = useState(initialValue);
|
|
5
|
+
const value = controlledValue !== void 0 ? controlledValue : internalValue;
|
|
6
|
+
const setValue = useCallback(
|
|
7
|
+
(newValue) => {
|
|
8
|
+
if (controlledValue === void 0) {
|
|
9
|
+
setInternalValue(newValue);
|
|
10
|
+
}
|
|
11
|
+
if (onChange) {
|
|
12
|
+
onChange(newValue);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
[controlledValue, onChange]
|
|
16
|
+
);
|
|
17
|
+
return [value, setValue];
|
|
18
|
+
}
|
|
19
|
+
function useQueryState(options) {
|
|
20
|
+
const [sort, setSort] = useControlledStateHelper(
|
|
21
|
+
options.initialSort ?? null,
|
|
22
|
+
options.sort,
|
|
23
|
+
options.onSortChange
|
|
24
|
+
);
|
|
25
|
+
const [filter, setFilter] = useControlledStateHelper(
|
|
26
|
+
options.initialFilter,
|
|
27
|
+
options.filter,
|
|
28
|
+
options.onFilterChange
|
|
29
|
+
);
|
|
30
|
+
const [search, setSearch] = useControlledStateHelper(
|
|
31
|
+
options.initialSearch ?? "",
|
|
32
|
+
options.search,
|
|
33
|
+
options.onSearchChange
|
|
34
|
+
);
|
|
35
|
+
return useMemo(
|
|
36
|
+
() => ({ sort, setSort, filter, setFilter, search, setSearch }),
|
|
37
|
+
[sort, setSort, filter, setFilter, search, setSearch]
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { useQueryState };
|
|
42
|
+
//# sourceMappingURL=useQueryState.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useQueryState.esm.js","sources":["../../../../src/components/Table/hooks/useQueryState.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useMemo, useState, useCallback } from 'react';\nimport type { QueryOptions, QueryState } from './types';\n\nfunction useControlledStateHelper<T, TControlled = T, TInitial = TControlled>(\n initialValue: TInitial,\n controlledValue: TControlled | undefined,\n onChange: ((value: T) => void) | undefined,\n) {\n const [internalValue, setInternalValue] = useState<TInitial>(initialValue);\n\n const value = controlledValue !== undefined ? controlledValue : internalValue;\n\n const setValue = useCallback(\n (newValue: T) => {\n if (controlledValue === undefined) {\n setInternalValue(newValue as unknown as TInitial);\n }\n if (onChange) {\n onChange(newValue);\n }\n },\n [controlledValue, onChange],\n );\n\n return [value, setValue] as const;\n}\n\n/** @internal */\nexport function useQueryState<TFilter>(\n options: QueryOptions<TFilter>,\n): QueryState<TFilter> {\n const [sort, setSort] = useControlledStateHelper(\n options.initialSort ?? null,\n options.sort,\n options.onSortChange,\n );\n const [filter, setFilter] = useControlledStateHelper(\n options.initialFilter,\n options.filter,\n options.onFilterChange,\n );\n const [search, setSearch] = useControlledStateHelper(\n options.initialSearch ?? '',\n options.search,\n options.onSearchChange,\n );\n\n return useMemo(\n () => ({ sort, setSort, filter, setFilter, search, setSearch }),\n [sort, setSort, filter, setFilter, search, setSearch],\n );\n}\n"],"names":[],"mappings":";;AAmBA,SAAS,wBAAA,CACP,YAAA,EACA,eAAA,EACA,QAAA,EACA;AACA,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAmB,YAAY,CAAA;AAEzE,EAAA,MAAM,KAAA,GAAQ,eAAA,KAAoB,MAAA,GAAY,eAAA,GAAkB,aAAA;AAEhE,EAAA,MAAM,QAAA,GAAW,WAAA;AAAA,IACf,CAAC,QAAA,KAAgB;AACf,MAAA,IAAI,oBAAoB,MAAA,EAAW;AACjC,QAAA,gBAAA,CAAiB,QAA+B,CAAA;AAAA,MAClD;AACA,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,QAAA,CAAS,QAAQ,CAAA;AAAA,MACnB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,iBAAiB,QAAQ;AAAA,GAC5B;AAEA,EAAA,OAAO,CAAC,OAAO,QAAQ,CAAA;AACzB;AAGO,SAAS,cACd,OAAA,EACqB;AACrB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,wBAAA;AAAA,IACtB,QAAQ,WAAA,IAAe,IAAA;AAAA,IACvB,OAAA,CAAQ,IAAA;AAAA,IACR,OAAA,CAAQ;AAAA,GACV;AACA,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,wBAAA;AAAA,IAC1B,OAAA,CAAQ,aAAA;AAAA,IACR,OAAA,CAAQ,MAAA;AAAA,IACR,OAAA,CAAQ;AAAA,GACV;AACA,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,wBAAA;AAAA,IAC1B,QAAQ,aAAA,IAAiB,EAAA;AAAA,IACzB,OAAA,CAAQ,MAAA;AAAA,IACR,OAAA,CAAQ;AAAA,GACV;AAEA,EAAA,OAAO,OAAA;AAAA,IACL,OAAO,EAAE,IAAA,EAAM,SAAS,MAAA,EAAQ,SAAA,EAAW,QAAQ,SAAA,EAAU,CAAA;AAAA,IAC7D,CAAC,IAAA,EAAM,OAAA,EAAS,MAAA,EAAQ,SAAA,EAAW,QAAQ,SAAS;AAAA,GACtD;AACF;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function useStableCallback(fn) {
|
|
4
|
+
const ref = useRef(fn);
|
|
5
|
+
ref.current = fn;
|
|
6
|
+
return useCallback((...args) => ref.current(...args), []);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { useStableCallback };
|
|
10
|
+
//# sourceMappingURL=useStableCallback.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStableCallback.esm.js","sources":["../../../../src/components/Table/hooks/useStableCallback.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useRef, useCallback } from 'react';\n\n/**\n * Returns a stable callback reference that always calls the latest version\n * of the provided function. Useful for callbacks passed as props that may\n * change on every render but shouldn't trigger effect re-runs.\n *\n * @internal\n */\nexport function useStableCallback<T extends (...args: any[]) => any>(fn: T): T {\n const ref = useRef(fn);\n ref.current = fn;\n\n return useCallback((...args: Parameters<T>) => ref.current(...args), []) as T;\n}\n"],"names":[],"mappings":";;AAyBO,SAAS,kBAAqD,EAAA,EAAU;AAC7E,EAAA,MAAM,GAAA,GAAM,OAAO,EAAE,CAAA;AACrB,EAAA,GAAA,CAAI,OAAA,GAAU,EAAA;AAEd,EAAA,OAAO,WAAA,CAAY,IAAI,IAAA,KAAwB,GAAA,CAAI,QAAQ,GAAG,IAAI,CAAA,EAAG,EAAE,CAAA;AACzE;;;;"}
|
|
@@ -1,114 +1,95 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef, useMemo } from 'react';
|
|
2
|
+
import { useQueryState } from './useQueryState.esm.js';
|
|
3
|
+
import { useCompletePagination } from './useCompletePagination.esm.js';
|
|
4
|
+
import { useCursorPagination } from './useCursorPagination.esm.js';
|
|
5
|
+
import { useOffsetPagination } from './useOffsetPagination.esm.js';
|
|
2
6
|
|
|
3
|
-
function
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
defaultOffset = 0,
|
|
13
|
-
onNextPage,
|
|
14
|
-
onPreviousPage,
|
|
15
|
-
showPageSizeOptions = true
|
|
16
|
-
} = paginationConfig;
|
|
17
|
-
const isControlled = controlledOffset !== void 0 || controlledPageSize !== void 0;
|
|
18
|
-
const rowCount = providedRowCount ?? data?.length ?? 0;
|
|
19
|
-
const [internalOffset, setInternalOffset] = useState(defaultOffset);
|
|
20
|
-
const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
|
|
21
|
-
const currentOffset = controlledOffset ?? internalOffset;
|
|
22
|
-
const currentPageSize = controlledPageSize ?? internalPageSize;
|
|
23
|
-
const currentData = useMemo(() => {
|
|
24
|
-
if (!data) return void 0;
|
|
25
|
-
return data.slice(currentOffset, currentOffset + currentPageSize);
|
|
26
|
-
}, [data, currentOffset, currentPageSize]);
|
|
27
|
-
const setOffset = useCallback(
|
|
28
|
-
(newOffset) => {
|
|
29
|
-
if (isControlled) {
|
|
30
|
-
onOffsetChange?.(newOffset);
|
|
31
|
-
} else {
|
|
32
|
-
setInternalOffset(newOffset);
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
[isControlled, onOffsetChange]
|
|
36
|
-
);
|
|
37
|
-
const setPageSize = useCallback(
|
|
38
|
-
(newPageSize) => {
|
|
39
|
-
const newOffset = 0;
|
|
40
|
-
if (isControlled) {
|
|
41
|
-
onPageSizeChange?.(newPageSize);
|
|
42
|
-
onOffsetChange?.(newOffset);
|
|
43
|
-
} else {
|
|
44
|
-
setInternalPageSize(newPageSize);
|
|
45
|
-
setInternalOffset(newOffset);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
[isControlled, onPageSizeChange, onOffsetChange]
|
|
49
|
-
);
|
|
50
|
-
const nextPage = useCallback(() => {
|
|
51
|
-
const nextOffset = currentOffset + currentPageSize;
|
|
52
|
-
if (nextOffset < rowCount) {
|
|
53
|
-
onNextPage?.();
|
|
54
|
-
setOffset(nextOffset);
|
|
55
|
-
}
|
|
56
|
-
}, [currentOffset, currentPageSize, rowCount, onNextPage, setOffset]);
|
|
57
|
-
const previousPage = useCallback(() => {
|
|
58
|
-
if (currentOffset > 0) {
|
|
59
|
-
onPreviousPage?.();
|
|
60
|
-
const prevOffset = Math.max(0, currentOffset - currentPageSize);
|
|
61
|
-
setOffset(prevOffset);
|
|
62
|
-
}
|
|
63
|
-
}, [currentOffset, currentPageSize, onPreviousPage, setOffset]);
|
|
64
|
-
const paginationProps = useMemo(
|
|
7
|
+
function useTableProps(paginationResult, sortState, paginationOptions = {}) {
|
|
8
|
+
const { showPageSizeOptions = true, getLabel } = paginationOptions;
|
|
9
|
+
const previousDataRef = useRef(paginationResult.data);
|
|
10
|
+
if (paginationResult.data) {
|
|
11
|
+
previousDataRef.current = paginationResult.data;
|
|
12
|
+
}
|
|
13
|
+
const displayData = paginationResult.data ?? previousDataRef.current;
|
|
14
|
+
const isStale = paginationResult.loading && displayData !== void 0;
|
|
15
|
+
const pagination = useMemo(
|
|
65
16
|
() => ({
|
|
66
|
-
|
|
67
|
-
pageSize:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
17
|
+
type: "page",
|
|
18
|
+
pageSize: paginationResult.pageSize,
|
|
19
|
+
offset: paginationResult.offset,
|
|
20
|
+
totalCount: paginationResult.totalCount,
|
|
21
|
+
hasNextPage: paginationResult.hasNextPage,
|
|
22
|
+
hasPreviousPage: paginationResult.hasPreviousPage,
|
|
23
|
+
onNextPage: paginationResult.onNextPage,
|
|
24
|
+
onPreviousPage: paginationResult.onPreviousPage,
|
|
25
|
+
onPageSizeChange: paginationResult.onPageSizeChange,
|
|
26
|
+
showPageSizeOptions,
|
|
27
|
+
getLabel
|
|
74
28
|
}),
|
|
75
29
|
[
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
onNextPage,
|
|
82
|
-
onPreviousPage,
|
|
83
|
-
|
|
30
|
+
paginationResult.pageSize,
|
|
31
|
+
paginationResult.offset,
|
|
32
|
+
paginationResult.totalCount,
|
|
33
|
+
paginationResult.hasNextPage,
|
|
34
|
+
paginationResult.hasPreviousPage,
|
|
35
|
+
paginationResult.onNextPage,
|
|
36
|
+
paginationResult.onPreviousPage,
|
|
37
|
+
paginationResult.onPageSizeChange
|
|
84
38
|
]
|
|
85
39
|
);
|
|
86
|
-
|
|
40
|
+
return useMemo(
|
|
87
41
|
() => ({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
setOffset,
|
|
95
|
-
setPageSize
|
|
42
|
+
data: displayData,
|
|
43
|
+
loading: paginationResult.loading,
|
|
44
|
+
isStale,
|
|
45
|
+
error: paginationResult.error,
|
|
46
|
+
pagination,
|
|
47
|
+
sort: sortState
|
|
96
48
|
}),
|
|
97
49
|
[
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
50
|
+
displayData,
|
|
51
|
+
paginationResult.loading,
|
|
52
|
+
isStale,
|
|
53
|
+
paginationResult.error,
|
|
54
|
+
pagination,
|
|
55
|
+
showPageSizeOptions,
|
|
56
|
+
getLabel,
|
|
57
|
+
sortState
|
|
106
58
|
]
|
|
107
59
|
);
|
|
60
|
+
}
|
|
61
|
+
function useTable(options) {
|
|
62
|
+
const query = useQueryState(options);
|
|
63
|
+
const initialModeRef = useRef(options.mode);
|
|
64
|
+
if (initialModeRef.current !== options.mode) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`useTable mode cannot change from '${initialModeRef.current}' to '${options.mode}'. The mode must remain stable for the lifetime of the component.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
let pagination;
|
|
70
|
+
if (options.mode === "complete") {
|
|
71
|
+
pagination = useCompletePagination(options, query);
|
|
72
|
+
} else if (options.mode === "offset") {
|
|
73
|
+
pagination = useOffsetPagination(options, query);
|
|
74
|
+
} else if (options.mode === "cursor") {
|
|
75
|
+
pagination = useCursorPagination(options, query);
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error("Invalid mode");
|
|
78
|
+
}
|
|
79
|
+
const sortState = useMemo(
|
|
80
|
+
() => ({ descriptor: query.sort, onSortChange: query.setSort }),
|
|
81
|
+
[query.sort, query.setSort]
|
|
82
|
+
);
|
|
83
|
+
const tableProps = useTableProps(
|
|
84
|
+
pagination,
|
|
85
|
+
sortState,
|
|
86
|
+
options.paginationOptions ?? {}
|
|
87
|
+
);
|
|
108
88
|
return {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
89
|
+
tableProps,
|
|
90
|
+
reload: pagination.reload,
|
|
91
|
+
filter: { value: query.filter, onChange: query.setFilter },
|
|
92
|
+
search: { value: query.search, onChange: query.setSearch }
|
|
112
93
|
};
|
|
113
94
|
}
|
|
114
95
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTable.esm.js","sources":["../../../../src/components/Table/hooks/useTable.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\
|
|
1
|
+
{"version":3,"file":"useTable.esm.js","sources":["../../../../src/components/Table/hooks/useTable.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useMemo, useRef } from 'react';\nimport type { SortState, TableItem, TableProps } from '../types';\nimport type {\n PaginationOptions,\n PaginationResult,\n UseTableOptions,\n UseTableResult,\n} from './types';\nimport { useQueryState } from './useQueryState';\nimport { useCompletePagination } from './useCompletePagination';\nimport { useCursorPagination } from './useCursorPagination';\nimport { useOffsetPagination } from './useOffsetPagination';\n\nfunction useTableProps<T extends TableItem>(\n paginationResult: PaginationResult<T>,\n sortState: SortState,\n paginationOptions: PaginationOptions = {},\n): Omit<\n TableProps<T>,\n 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState'\n> {\n const { showPageSizeOptions = true, getLabel } = paginationOptions;\n\n const previousDataRef = useRef(paginationResult.data);\n if (paginationResult.data) {\n previousDataRef.current = paginationResult.data;\n }\n\n const displayData = paginationResult.data ?? previousDataRef.current;\n const isStale = paginationResult.loading && displayData !== undefined;\n\n const pagination = useMemo(\n () => ({\n type: 'page' as const,\n pageSize: paginationResult.pageSize,\n offset: paginationResult.offset,\n totalCount: paginationResult.totalCount,\n hasNextPage: paginationResult.hasNextPage,\n hasPreviousPage: paginationResult.hasPreviousPage,\n onNextPage: paginationResult.onNextPage,\n onPreviousPage: paginationResult.onPreviousPage,\n onPageSizeChange: paginationResult.onPageSizeChange,\n showPageSizeOptions,\n getLabel,\n }),\n [\n paginationResult.pageSize,\n paginationResult.offset,\n paginationResult.totalCount,\n paginationResult.hasNextPage,\n paginationResult.hasPreviousPage,\n paginationResult.onNextPage,\n paginationResult.onPreviousPage,\n paginationResult.onPageSizeChange,\n ],\n );\n\n return useMemo(\n () => ({\n data: displayData,\n loading: paginationResult.loading,\n isStale,\n error: paginationResult.error,\n pagination,\n sort: sortState,\n }),\n [\n displayData,\n paginationResult.loading,\n isStale,\n paginationResult.error,\n pagination,\n showPageSizeOptions,\n getLabel,\n sortState,\n ],\n );\n}\n\n/** @public */\nexport function useTable<T extends TableItem, TFilter = unknown>(\n options: UseTableOptions<T, TFilter>,\n): UseTableResult<T, TFilter> {\n const query = useQueryState<TFilter>(options);\n\n const initialModeRef = useRef(options.mode);\n if (initialModeRef.current !== options.mode) {\n throw new Error(\n `useTable mode cannot change from '${initialModeRef.current}' to '${options.mode}'. ` +\n `The mode must remain stable for the lifetime of the component.`,\n );\n }\n\n let pagination: PaginationResult<T> & { reload: () => void };\n\n if (options.mode === 'complete') {\n pagination = useCompletePagination(options, query);\n } else if (options.mode === 'offset') {\n pagination = useOffsetPagination(options, query);\n } else if (options.mode === 'cursor') {\n pagination = useCursorPagination(options, query);\n } else {\n throw new Error('Invalid mode');\n }\n\n const sortState: SortState = useMemo(\n () => ({ descriptor: query.sort, onSortChange: query.setSort }),\n [query.sort, query.setSort],\n );\n\n const tableProps = useTableProps(\n pagination,\n sortState,\n options.paginationOptions ?? {},\n );\n\n return {\n tableProps,\n reload: pagination.reload,\n filter: { value: query.filter, onChange: query.setFilter },\n search: { value: query.search, onChange: query.setSearch },\n };\n}\n"],"names":[],"mappings":"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"}
|