@backstage/ui 0.10.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 +77 -0
- package/css/styles.css +43 -17
- 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/Header/Header.module.css.esm.js +1 -1
- package/dist/components/Link/Link.esm.js +9 -6
- package/dist/components/Link/Link.esm.js.map +1 -1
- package/dist/components/SearchField/SearchField.esm.js +14 -21
- package/dist/components/SearchField/SearchField.esm.js.map +1 -1
- package/dist/components/SearchField/SearchField.module.css.esm.js +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,106 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
2
|
+
import { useStableCallback } from './useStableCallback.esm.js';
|
|
3
|
+
|
|
4
|
+
function useCompletePagination(options, query) {
|
|
5
|
+
const {
|
|
6
|
+
getData: getDataProp,
|
|
7
|
+
paginationOptions = {},
|
|
8
|
+
sortFn,
|
|
9
|
+
filterFn,
|
|
10
|
+
searchFn
|
|
11
|
+
} = options;
|
|
12
|
+
const { pageSize: defaultPageSize = 20, initialOffset = 0 } = paginationOptions;
|
|
13
|
+
const getData = useStableCallback(getDataProp);
|
|
14
|
+
const { sort, filter, search } = query;
|
|
15
|
+
const [items, setItems] = useState([]);
|
|
16
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
17
|
+
const [error, setError] = useState(void 0);
|
|
18
|
+
const [loadCount, setLoadCount] = useState(0);
|
|
19
|
+
const [offset, setOffset] = useState(initialOffset);
|
|
20
|
+
const [pageSize, setPageSize] = useState(defaultPageSize);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
setIsLoading(true);
|
|
24
|
+
setError(void 0);
|
|
25
|
+
(async () => {
|
|
26
|
+
try {
|
|
27
|
+
const result = getData();
|
|
28
|
+
const data = result instanceof Promise ? await result : result;
|
|
29
|
+
if (!cancelled) {
|
|
30
|
+
setItems(data);
|
|
31
|
+
setIsLoading(false);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (!cancelled) {
|
|
35
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
36
|
+
setIsLoading(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
return () => {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
};
|
|
43
|
+
}, [getData, loadCount]);
|
|
44
|
+
const prevQueryRef = useRef(query);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (prevQueryRef.current !== query) {
|
|
47
|
+
prevQueryRef.current = query;
|
|
48
|
+
setOffset(0);
|
|
49
|
+
}
|
|
50
|
+
}, [query]);
|
|
51
|
+
const processedData = useMemo(() => {
|
|
52
|
+
let result = [...items];
|
|
53
|
+
if (filter !== void 0 && filterFn) {
|
|
54
|
+
result = filterFn(result, filter);
|
|
55
|
+
}
|
|
56
|
+
if (search && searchFn) {
|
|
57
|
+
result = searchFn(result, search);
|
|
58
|
+
}
|
|
59
|
+
if (sort && sortFn) {
|
|
60
|
+
result = sortFn(result, sort);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}, [items, sort, filter, search, filterFn, searchFn, sortFn]);
|
|
64
|
+
const totalCount = processedData.length;
|
|
65
|
+
const paginatedData = useMemo(
|
|
66
|
+
() => processedData.slice(offset, offset + pageSize),
|
|
67
|
+
[processedData, offset, pageSize]
|
|
68
|
+
);
|
|
69
|
+
const hasNextPage = offset + pageSize < totalCount;
|
|
70
|
+
const hasPreviousPage = offset > 0;
|
|
71
|
+
const onNextPage = useCallback(() => {
|
|
72
|
+
if (offset + pageSize < totalCount) {
|
|
73
|
+
setOffset(offset + pageSize);
|
|
74
|
+
}
|
|
75
|
+
}, [offset, pageSize, totalCount]);
|
|
76
|
+
const onPreviousPage = useCallback(() => {
|
|
77
|
+
if (offset > 0) {
|
|
78
|
+
setOffset(Math.max(0, offset - pageSize));
|
|
79
|
+
}
|
|
80
|
+
}, [offset, pageSize]);
|
|
81
|
+
const onPageSizeChange = useCallback((newSize) => {
|
|
82
|
+
setPageSize(newSize);
|
|
83
|
+
setOffset(0);
|
|
84
|
+
}, []);
|
|
85
|
+
const reload = useCallback(() => {
|
|
86
|
+
setOffset(0);
|
|
87
|
+
setLoadCount((c) => c + 1);
|
|
88
|
+
}, []);
|
|
89
|
+
return {
|
|
90
|
+
data: paginatedData,
|
|
91
|
+
loading: isLoading,
|
|
92
|
+
error,
|
|
93
|
+
totalCount,
|
|
94
|
+
offset,
|
|
95
|
+
pageSize,
|
|
96
|
+
hasNextPage,
|
|
97
|
+
hasPreviousPage,
|
|
98
|
+
onNextPage,
|
|
99
|
+
onPreviousPage,
|
|
100
|
+
onPageSizeChange,
|
|
101
|
+
reload
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { useCompletePagination };
|
|
106
|
+
//# sourceMappingURL=useCompletePagination.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCompletePagination.esm.js","sources":["../../../../src/components/Table/hooks/useCompletePagination.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, useMemo, useEffect, useRef } from 'react';\nimport type { TableItem } from '../types';\nimport type {\n PaginationResult,\n QueryState,\n UseTableCompleteOptions,\n} from './types';\nimport { useStableCallback } from './useStableCallback';\n\n/** @internal */\nexport function useCompletePagination<T extends TableItem, TFilter>(\n options: UseTableCompleteOptions<T, TFilter>,\n query: QueryState<TFilter>,\n): PaginationResult<T> & { reload: () => void } {\n const {\n getData: getDataProp,\n paginationOptions = {},\n sortFn,\n filterFn,\n searchFn,\n } = 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 [items, setItems] = useState<T[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loadCount, setLoadCount] = useState(0);\n\n const [offset, setOffset] = useState(initialOffset);\n const [pageSize, setPageSize] = useState(defaultPageSize);\n\n // Load data on mount and when loadCount changes (reload trigger)\n useEffect(() => {\n let cancelled = false;\n setIsLoading(true);\n setError(undefined);\n\n (async () => {\n try {\n const result = getData();\n const data = result instanceof Promise ? await result : result;\n if (!cancelled) {\n setItems(data);\n setIsLoading(false);\n }\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [getData, loadCount]);\n\n // Reset offset when query changes (query object is memoized)\n const prevQueryRef = useRef(query);\n useEffect(() => {\n if (prevQueryRef.current !== query) {\n prevQueryRef.current = query;\n setOffset(0);\n }\n }, [query]);\n\n // Process data client-side (filter, search, sort)\n const processedData = useMemo(() => {\n let result = [...items];\n if (filter !== undefined && filterFn) {\n result = filterFn(result, filter);\n }\n if (search && searchFn) {\n result = searchFn(result, search);\n }\n if (sort && sortFn) {\n result = sortFn(result, sort);\n }\n return result;\n }, [items, sort, filter, search, filterFn, searchFn, sortFn]);\n\n const totalCount = processedData.length;\n\n // Paginate the processed data\n const paginatedData = useMemo(\n () => processedData.slice(offset, offset + pageSize),\n [processedData, offset, pageSize],\n );\n\n const hasNextPage = offset + pageSize < totalCount;\n const hasPreviousPage = offset > 0;\n\n const onNextPage = useCallback(() => {\n if (offset + pageSize < totalCount) {\n setOffset(offset + pageSize);\n }\n }, [offset, pageSize, totalCount]);\n\n const onPreviousPage = useCallback(() => {\n if (offset > 0) {\n setOffset(Math.max(0, offset - pageSize));\n }\n }, [offset, pageSize]);\n\n const onPageSizeChange = useCallback((newSize: number) => {\n setPageSize(newSize);\n setOffset(0);\n }, []);\n\n const reload = useCallback(() => {\n setOffset(0);\n setLoadCount(c => c + 1);\n }, []);\n\n return {\n data: paginatedData,\n loading: isLoading,\n error,\n totalCount,\n offset,\n pageSize,\n hasNextPage,\n hasPreviousPage,\n onNextPage,\n onPreviousPage,\n onPageSizeChange,\n reload,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { usePageCache } from './usePageCache.esm.js';
|
|
3
|
+
import { useStableCallback } from './useStableCallback.esm.js';
|
|
4
|
+
import { useDebouncedReload } from './useDebouncedReload.esm.js';
|
|
5
|
+
|
|
6
|
+
function useCursorPagination(options, query) {
|
|
7
|
+
const { getData: getDataProp, paginationOptions = {} } = options;
|
|
8
|
+
const { pageSize: defaultPageSize = 20 } = paginationOptions;
|
|
9
|
+
const getData = useStableCallback(getDataProp);
|
|
10
|
+
const { sort, filter, search } = query;
|
|
11
|
+
const [pageSize, setPageSize] = useState(defaultPageSize);
|
|
12
|
+
const wrappedGetData = useCallback(
|
|
13
|
+
async ({
|
|
14
|
+
cursor,
|
|
15
|
+
signal
|
|
16
|
+
}) => {
|
|
17
|
+
const params = {
|
|
18
|
+
cursor,
|
|
19
|
+
pageSize,
|
|
20
|
+
sort,
|
|
21
|
+
filter,
|
|
22
|
+
search,
|
|
23
|
+
signal
|
|
24
|
+
};
|
|
25
|
+
const response = await getData(params);
|
|
26
|
+
return {
|
|
27
|
+
data: response.data,
|
|
28
|
+
prevCursor: response.prevCursor,
|
|
29
|
+
nextCursor: response.nextCursor,
|
|
30
|
+
totalCount: response.totalCount
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
[getData, pageSize, sort, filter, search]
|
|
34
|
+
);
|
|
35
|
+
const cache = usePageCache({ getData: wrappedGetData });
|
|
36
|
+
useDebouncedReload(query, pageSize, cache.reload);
|
|
37
|
+
const onPageSizeChange = useCallback(
|
|
38
|
+
(newSize) => setPageSize(newSize),
|
|
39
|
+
[]
|
|
40
|
+
);
|
|
41
|
+
return {
|
|
42
|
+
data: cache.data,
|
|
43
|
+
loading: cache.loading,
|
|
44
|
+
error: cache.error,
|
|
45
|
+
totalCount: cache.totalCount,
|
|
46
|
+
offset: void 0,
|
|
47
|
+
pageSize,
|
|
48
|
+
hasNextPage: cache.hasNextPage,
|
|
49
|
+
hasPreviousPage: cache.hasPreviousPage,
|
|
50
|
+
onNextPage: cache.onNextPage,
|
|
51
|
+
onPreviousPage: cache.onPreviousPage,
|
|
52
|
+
onPageSizeChange,
|
|
53
|
+
reload: cache.reload
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { useCursorPagination };
|
|
58
|
+
//# sourceMappingURL=useCursorPagination.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCursorPagination.esm.js","sources":["../../../../src/components/Table/hooks/useCursorPagination.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 UseTableCursorOptions,\n CursorParams,\n QueryState,\n PaginationResult,\n} from './types';\nimport { usePageCache } from './usePageCache';\nimport { useStableCallback } from './useStableCallback';\nimport { useDebouncedReload } from './useDebouncedReload';\n\nexport function useCursorPagination<T extends TableItem, TFilter>(\n options: UseTableCursorOptions<T, TFilter>,\n query: QueryState<TFilter>,\n): PaginationResult<T> & { reload: () => void } {\n const { getData: getDataProp, paginationOptions = {} } = options;\n const { pageSize: defaultPageSize = 20 } = 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: string | undefined;\n signal: AbortSignal;\n }) => {\n const params: CursorParams<TFilter> = {\n cursor,\n pageSize,\n sort,\n filter,\n search,\n signal,\n };\n\n const response = await getData(params);\n\n return {\n data: response.data,\n prevCursor: response.prevCursor,\n nextCursor: response.nextCursor,\n totalCount: response.totalCount,\n };\n },\n [getData, pageSize, sort, filter, search],\n );\n\n const cache = usePageCache<T, string>({ getData: wrappedGetData });\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: undefined,\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,EAAG,GAAI,iBAAA;AAE3C,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,MAAA,GAAgC;AAAA,QACpC,MAAA;AAAA,QACA,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,OAAO;AAAA,QACL,MAAM,QAAA,CAAS,IAAA;AAAA,QACf,YAAY,QAAA,CAAS,UAAA;AAAA,QACrB,YAAY,QAAA,CAAS,UAAA;AAAA,QACrB,YAAY,QAAA,CAAS;AAAA,OACvB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,QAAA,EAAU,IAAA,EAAM,QAAQ,MAAM;AAAA,GAC1C;AAEA,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAwB,EAAE,OAAA,EAAS,gBAAgB,CAAA;AAEjE,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,MAAA;AAAA,IACR,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,17 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
function useDebouncedReload(query, pageSize, reload, delay = 200) {
|
|
4
|
+
const prevDepsRef = useRef({ query, pageSize });
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const prev = prevDepsRef.current;
|
|
7
|
+
if (prev.query !== query || prev.pageSize !== pageSize) {
|
|
8
|
+
prevDepsRef.current = { query, pageSize };
|
|
9
|
+
const timer = setTimeout(reload, delay);
|
|
10
|
+
return () => clearTimeout(timer);
|
|
11
|
+
}
|
|
12
|
+
return void 0;
|
|
13
|
+
}, [query, pageSize, reload, delay]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { useDebouncedReload };
|
|
17
|
+
//# sourceMappingURL=useDebouncedReload.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDebouncedReload.esm.js","sources":["../../../../src/components/Table/hooks/useDebouncedReload.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 { useEffect, useRef } from 'react';\nimport type { QueryState } from './types';\n\n/**\n * Triggers a debounced reload when query or pageSize changes.\n * Debouncing reduces backend load during rapid changes (e.g., typing in search).\n */\n/** @internal */\nexport function useDebouncedReload<TFilter>(\n query: QueryState<TFilter>,\n pageSize: number,\n reload: () => void,\n delay: number = 200,\n): void {\n const prevDepsRef = useRef({ query, pageSize });\n\n useEffect(() => {\n const prev = prevDepsRef.current;\n if (prev.query !== query || prev.pageSize !== pageSize) {\n prevDepsRef.current = { query, pageSize };\n const timer = setTimeout(reload, delay);\n return () => clearTimeout(timer);\n }\n return undefined;\n }, [query, pageSize, reload, delay]);\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,kBAAA,CACd,KAAA,EACA,QAAA,EACA,MAAA,EACA,QAAgB,GAAA,EACV;AACN,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,EAAE,KAAA,EAAO,UAAU,CAAA;AAE9C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,OAAO,WAAA,CAAY,OAAA;AACzB,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,KAAA,IAAS,IAAA,CAAK,aAAa,QAAA,EAAU;AACtD,MAAA,WAAA,CAAY,OAAA,GAAU,EAAE,KAAA,EAAO,QAAA,EAAS;AACxC,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,MAAA,EAAQ,KAAK,CAAA;AACtC,MAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,IACjC;AACA,IAAA,OAAO,MAAA;AAAA,EACT,GAAG,CAAC,KAAA,EAAO,QAAA,EAAU,MAAA,EAAQ,KAAK,CAAC,CAAA;AACrC;;;;"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { usePageCache } from './usePageCache.esm.js';
|
|
3
|
+
import { useStableCallback } from './useStableCallback.esm.js';
|
|
4
|
+
import { useDebouncedReload } from './useDebouncedReload.esm.js';
|
|
5
|
+
|
|
6
|
+
function useOffsetPagination(options, query) {
|
|
7
|
+
const { getData: getDataProp, paginationOptions = {} } = options;
|
|
8
|
+
const { pageSize: defaultPageSize = 20, initialOffset = 0 } = paginationOptions;
|
|
9
|
+
const getData = useStableCallback(getDataProp);
|
|
10
|
+
const { sort, filter, search } = query;
|
|
11
|
+
const [pageSize, setPageSize] = useState(defaultPageSize);
|
|
12
|
+
const wrappedGetData = useCallback(
|
|
13
|
+
async ({
|
|
14
|
+
cursor,
|
|
15
|
+
signal
|
|
16
|
+
}) => {
|
|
17
|
+
const currentOffset = cursor ?? 0;
|
|
18
|
+
const params = {
|
|
19
|
+
offset: currentOffset,
|
|
20
|
+
pageSize,
|
|
21
|
+
sort,
|
|
22
|
+
filter,
|
|
23
|
+
search,
|
|
24
|
+
signal
|
|
25
|
+
};
|
|
26
|
+
const response = await getData(params);
|
|
27
|
+
const prevCursor = currentOffset > 0 ? Math.max(0, currentOffset - pageSize) : void 0;
|
|
28
|
+
const nextCursor = currentOffset + pageSize < response.totalCount ? currentOffset + pageSize : void 0;
|
|
29
|
+
return {
|
|
30
|
+
data: response.data,
|
|
31
|
+
prevCursor,
|
|
32
|
+
nextCursor,
|
|
33
|
+
totalCount: response.totalCount
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
[getData, pageSize, sort, filter, search]
|
|
37
|
+
);
|
|
38
|
+
const cache = usePageCache({
|
|
39
|
+
getData: wrappedGetData,
|
|
40
|
+
initialCurrentCursor: initialOffset > 0 ? initialOffset : void 0
|
|
41
|
+
});
|
|
42
|
+
useDebouncedReload(query, pageSize, cache.reload);
|
|
43
|
+
const onPageSizeChange = useCallback(
|
|
44
|
+
(newSize) => setPageSize(newSize),
|
|
45
|
+
[]
|
|
46
|
+
);
|
|
47
|
+
return {
|
|
48
|
+
data: cache.data,
|
|
49
|
+
loading: cache.loading,
|
|
50
|
+
error: cache.error,
|
|
51
|
+
totalCount: cache.totalCount,
|
|
52
|
+
offset: cache.currentCursor ?? 0,
|
|
53
|
+
pageSize,
|
|
54
|
+
hasNextPage: cache.hasNextPage,
|
|
55
|
+
hasPreviousPage: cache.hasPreviousPage,
|
|
56
|
+
onNextPage: cache.onNextPage,
|
|
57
|
+
onPreviousPage: cache.onPreviousPage,
|
|
58
|
+
onPageSizeChange,
|
|
59
|
+
reload: cache.reload
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { useOffsetPagination };
|
|
64
|
+
//# sourceMappingURL=useOffsetPagination.esm.js.map
|
|
@@ -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;;;;"}
|