@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/css/styles.css +36 -12
  3. package/dist/components/Box/Box.esm.js +13 -6
  4. package/dist/components/Box/Box.esm.js.map +1 -1
  5. package/dist/components/Box/Box.module.css.esm.js +1 -1
  6. package/dist/components/Box/definition.esm.js +4 -1
  7. package/dist/components/Box/definition.esm.js.map +1 -1
  8. package/dist/components/Button/Button.esm.js +12 -1
  9. package/dist/components/Button/Button.esm.js.map +1 -1
  10. package/dist/components/Button/Button.module.css.esm.js +1 -1
  11. package/dist/components/Flex/Flex.esm.js +14 -6
  12. package/dist/components/Flex/Flex.esm.js.map +1 -1
  13. package/dist/components/Flex/Flex.module.css.esm.js +1 -1
  14. package/dist/components/Flex/definition.esm.js +4 -1
  15. package/dist/components/Flex/definition.esm.js.map +1 -1
  16. package/dist/components/Grid/Grid.esm.js +27 -12
  17. package/dist/components/Grid/Grid.esm.js.map +1 -1
  18. package/dist/components/Grid/Grid.module.css.esm.js +2 -2
  19. package/dist/components/Grid/definition.esm.js +8 -2
  20. package/dist/components/Grid/definition.esm.js.map +1 -1
  21. package/dist/components/Link/Link.esm.js +9 -6
  22. package/dist/components/Link/Link.esm.js.map +1 -1
  23. package/dist/components/Table/Table.module.css.esm.js +1 -1
  24. package/dist/components/Table/components/Table.esm.js +154 -15
  25. package/dist/components/Table/components/Table.esm.js.map +1 -1
  26. package/dist/components/Table/components/TableRoot.esm.js +26 -0
  27. package/dist/components/Table/components/TableRoot.esm.js.map +1 -0
  28. package/dist/components/Table/definition.esm.js +3 -0
  29. package/dist/components/Table/definition.esm.js.map +1 -1
  30. package/dist/components/Table/hooks/useCompletePagination.esm.js +106 -0
  31. package/dist/components/Table/hooks/useCompletePagination.esm.js.map +1 -0
  32. package/dist/components/Table/hooks/useCursorPagination.esm.js +58 -0
  33. package/dist/components/Table/hooks/useCursorPagination.esm.js.map +1 -0
  34. package/dist/components/Table/hooks/useDebouncedReload.esm.js +17 -0
  35. package/dist/components/Table/hooks/useDebouncedReload.esm.js.map +1 -0
  36. package/dist/components/Table/hooks/useOffsetPagination.esm.js +64 -0
  37. package/dist/components/Table/hooks/useOffsetPagination.esm.js.map +1 -0
  38. package/dist/components/Table/hooks/usePageCache.esm.js +168 -0
  39. package/dist/components/Table/hooks/usePageCache.esm.js.map +1 -0
  40. package/dist/components/Table/hooks/useQueryState.esm.js +42 -0
  41. package/dist/components/Table/hooks/useQueryState.esm.js.map +1 -0
  42. package/dist/components/Table/hooks/useStableCallback.esm.js +10 -0
  43. package/dist/components/Table/hooks/useStableCallback.esm.js.map +1 -0
  44. package/dist/components/Table/hooks/useTable.esm.js +80 -99
  45. package/dist/components/Table/hooks/useTable.esm.js.map +1 -1
  46. package/dist/components/TablePagination/TablePagination.esm.js +76 -101
  47. package/dist/components/TablePagination/TablePagination.esm.js.map +1 -1
  48. package/dist/components/ToggleButton/ToggleButton.esm.js +51 -0
  49. package/dist/components/ToggleButton/ToggleButton.esm.js.map +1 -0
  50. package/dist/components/ToggleButton/ToggleButton.module.css.esm.js +8 -0
  51. package/dist/components/ToggleButton/ToggleButton.module.css.esm.js.map +1 -0
  52. package/dist/components/ToggleButton/definition.esm.js +12 -0
  53. package/dist/components/ToggleButton/definition.esm.js.map +1 -0
  54. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.esm.js +33 -0
  55. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.esm.js.map +1 -0
  56. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.module.css.esm.js +8 -0
  57. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.module.css.esm.js.map +1 -0
  58. package/dist/components/ToggleButtonGroup/definition.esm.js +11 -0
  59. package/dist/components/ToggleButtonGroup/definition.esm.js.map +1 -0
  60. package/dist/hooks/useSurface.esm.js +74 -0
  61. package/dist/hooks/useSurface.esm.js.map +1 -0
  62. package/dist/index.d.ts +306 -92
  63. package/dist/index.esm.js +5 -0
  64. package/dist/index.esm.js.map +1 -1
  65. 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 { useState, useMemo, useCallback } from 'react';
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 useTable(config = {}) {
4
- const { data, pagination: paginationConfig = {} } = config;
5
- const {
6
- rowCount: providedRowCount,
7
- offset: controlledOffset,
8
- pageSize: controlledPageSize,
9
- onOffsetChange,
10
- onPageSizeChange,
11
- defaultPageSize = 10,
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
- offset: currentOffset,
67
- pageSize: currentPageSize,
68
- rowCount,
69
- setOffset,
70
- setPageSize,
71
- onNextPage,
72
- onPreviousPage,
73
- showPageSizeOptions
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
- currentOffset,
77
- currentPageSize,
78
- rowCount,
79
- setOffset,
80
- setPageSize,
81
- onNextPage,
82
- onPreviousPage,
83
- showPageSizeOptions
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
- const pagination = useMemo(
40
+ return useMemo(
87
41
  () => ({
88
- paginationProps,
89
- offset: currentOffset,
90
- pageSize: currentPageSize,
91
- data: currentData,
92
- nextPage,
93
- previousPage,
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
- paginationProps,
99
- currentOffset,
100
- currentPageSize,
101
- currentData,
102
- nextPage,
103
- previousPage,
104
- setOffset,
105
- setPageSize
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
- data: currentData,
110
- paginationProps,
111
- pagination
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 */\n\nimport { useState, useMemo, useCallback } from 'react';\nimport type { TablePaginationProps } from '../../TablePagination/types';\nimport type {\n UseTableConfig,\n UseTableResult,\n UseTablePagination,\n} from './types';\n\n/**\n * Hook for managing table state including pagination and future features like sorting.\n * Supports both controlled and uncontrolled modes using offset/pageSize pattern (Backstage style).\n *\n * @public\n */\nexport function useTable<T = any>(\n config: UseTableConfig<T> = {},\n): UseTableResult<T> {\n const { data, pagination: paginationConfig = {} } = config;\n\n const {\n rowCount: providedRowCount,\n offset: controlledOffset,\n pageSize: controlledPageSize,\n onOffsetChange,\n onPageSizeChange,\n defaultPageSize = 10,\n defaultOffset = 0,\n onNextPage,\n onPreviousPage,\n showPageSizeOptions = true,\n } = paginationConfig;\n\n // Determine if we're in controlled mode\n const isControlled =\n controlledOffset !== undefined || controlledPageSize !== undefined;\n\n // Use providedRowCount if passed, otherwise fallback to data length\n const rowCount = providedRowCount ?? data?.length ?? 0;\n\n // Internal state for uncontrolled mode\n const [internalOffset, setInternalOffset] = useState(defaultOffset);\n const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);\n\n // Calculate current values\n const currentOffset = controlledOffset ?? internalOffset;\n const currentPageSize = controlledPageSize ?? internalPageSize;\n\n // Calculate sliced data if data array is provided\n const currentData = useMemo(() => {\n if (!data) return undefined;\n return data.slice(currentOffset, currentOffset + currentPageSize);\n }, [data, currentOffset, currentPageSize]);\n\n // Update functions\n const setOffset = useCallback(\n (newOffset: number) => {\n if (isControlled) {\n onOffsetChange?.(newOffset);\n } else {\n setInternalOffset(newOffset);\n }\n },\n [isControlled, onOffsetChange],\n );\n\n const setPageSize = useCallback(\n (newPageSize: number) => {\n // When changing page size, reset to first page to avoid showing empty results\n const newOffset = 0;\n\n if (isControlled) {\n onPageSizeChange?.(newPageSize);\n onOffsetChange?.(newOffset);\n } else {\n setInternalPageSize(newPageSize);\n setInternalOffset(newOffset);\n }\n },\n [isControlled, onPageSizeChange, onOffsetChange],\n );\n\n const nextPage = useCallback(() => {\n const nextOffset = currentOffset + currentPageSize;\n if (nextOffset < rowCount) {\n onNextPage?.();\n setOffset(nextOffset);\n }\n }, [currentOffset, currentPageSize, rowCount, onNextPage, setOffset]);\n\n const previousPage = useCallback(() => {\n if (currentOffset > 0) {\n onPreviousPage?.();\n const prevOffset = Math.max(0, currentOffset - currentPageSize);\n setOffset(prevOffset);\n }\n }, [currentOffset, currentPageSize, onPreviousPage, setOffset]);\n\n // Pagination props for TablePagination component\n const paginationProps: TablePaginationProps = useMemo(\n () => ({\n offset: currentOffset,\n pageSize: currentPageSize,\n rowCount,\n setOffset,\n setPageSize,\n onNextPage,\n onPreviousPage,\n showPageSizeOptions,\n }),\n [\n currentOffset,\n currentPageSize,\n rowCount,\n setOffset,\n setPageSize,\n onNextPage,\n onPreviousPage,\n showPageSizeOptions,\n ],\n );\n\n const pagination: UseTablePagination<T> = useMemo(\n () => ({\n paginationProps,\n offset: currentOffset,\n pageSize: currentPageSize,\n data: currentData,\n nextPage,\n previousPage,\n setOffset,\n setPageSize,\n }),\n [\n paginationProps,\n currentOffset,\n currentPageSize,\n currentData,\n nextPage,\n previousPage,\n setOffset,\n setPageSize,\n ],\n );\n\n return {\n data: currentData,\n paginationProps,\n pagination,\n };\n}\n"],"names":[],"mappings":"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"}
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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"}