cursedbelt 5.20.0 → 5.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/hooks/useScrollMemory.d.ts +54 -0
- package/dist/react/hooks/useScrollMemory.d.ts.map +1 -0
- package/dist/react/hooks/useScrollMemory.js +212 -0
- package/dist/react/hooks/useScrollMemory.js.map +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/usePagedList.d.ts +24 -2
- package/dist/react/usePagedList.d.ts.map +1 -1
- package/dist/react/usePagedList.js +16 -3
- package/dist/react/usePagedList.js.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/core.css +2 -2
- package/package.json +2 -2
- package/scripts/guardrails-baseline.json +2 -1
- package/scripts/guardrailsEnforce.spec.ts +30 -0
- package/scripts/guardrailsEnforce.ts +39 -0
- package/src/react/hooks/useScrollMemory.spec.tsx +227 -0
- package/src/react/hooks/useScrollMemory.ts +245 -0
- package/src/react/index.ts +5 -0
- package/src/react/usePagedList.spec.tsx +78 -0
- package/src/react/usePagedList.ts +35 -3
- package/src/styles-areas/core.css +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type QueryKey, useInfiniteQuery } from '@tanstack/react-query';
|
|
1
|
+
import { keepPreviousData, type QueryKey, useInfiniteQuery } from '@tanstack/react-query';
|
|
2
|
+
import { useMemo } from 'react';
|
|
2
3
|
|
|
3
4
|
// ── usePagedList — one cursor-paged list layer over TanStack ─────────────────
|
|
4
5
|
// Every "cursor → items → nextCursor, append on Load more" list re-rolled the
|
|
@@ -18,6 +19,8 @@ export interface PagedPage<T> {
|
|
|
18
19
|
items: T[];
|
|
19
20
|
/** Opaque cursor for the next page; absent/undefined ⇒ no more pages. */
|
|
20
21
|
nextCursor?: string;
|
|
22
|
+
/** The size of the whole result, when the server counts it ("1,204 films"). */
|
|
23
|
+
total?: number;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export interface UsePagedListOptions<T> {
|
|
@@ -36,10 +39,23 @@ export interface UsePagedListOptions<T> {
|
|
|
36
39
|
pollWhile?: (items: T[]) => boolean;
|
|
37
40
|
/** ms a page stays fresh before a background refetch (TanStack `staleTime`). */
|
|
38
41
|
staleTime?: number;
|
|
42
|
+
/**
|
|
43
|
+
* ms an unwatched list's pages stay cached (TanStack `gcTime`, default 5 minutes). Raise it
|
|
44
|
+
* when the trip away is long — a film watched from a grid is two hours, and Back should still
|
|
45
|
+
* find every page.
|
|
46
|
+
*/
|
|
47
|
+
gcTime?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Keep the previous query's items on screen while a changed `queryKey` fetches its first
|
|
50
|
+
* page, instead of emptying the list — a search typed a letter at a time does not flash
|
|
51
|
+
* blank between keystrokes. `isLoading` stays false meanwhile; `isFetching` is true.
|
|
52
|
+
*/
|
|
53
|
+
keepPreviousItems?: boolean;
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
export interface PagedList<T> {
|
|
42
|
-
/** Every loaded page, flattened in order
|
|
57
|
+
/** Every loaded page, flattened in order — the same array until a page changes, so it is
|
|
58
|
+
* safe as a `useMemo` dependency. */
|
|
43
59
|
items: T[];
|
|
44
60
|
/** First load only (no pages yet) — drive skeletons off this, not `isFetching`. */
|
|
45
61
|
isLoading: boolean;
|
|
@@ -49,6 +65,8 @@ export interface PagedList<T> {
|
|
|
49
65
|
error: unknown;
|
|
50
66
|
/** Another page is available. */
|
|
51
67
|
hasMore: boolean;
|
|
68
|
+
/** The newest page's `total`, or `undefined` before the first page or when none carries one. */
|
|
69
|
+
total: number | undefined;
|
|
52
70
|
/** Load the next page (no-op if none remain or one is already loading). */
|
|
53
71
|
loadMore: () => void;
|
|
54
72
|
/** Refetch from the first page (every loaded page re-fetches). */
|
|
@@ -62,6 +80,11 @@ export interface PagedList<T> {
|
|
|
62
80
|
* `loadMore()` from a "Load more" button or an intersection observer.
|
|
63
81
|
*
|
|
64
82
|
* Requires `@tanstack/react-query` wrapped in a `QueryClientProvider`.
|
|
83
|
+
*
|
|
84
|
+
* **A list that is left and come back to keeps every page it had** (for the query cache's
|
|
85
|
+
* `gcTime`), in its first render — which is what lets `useScrollMemory` put the viewer back
|
|
86
|
+
* 30,000px down instead of at the end of page one. Give it a `staleTime` too, or the return
|
|
87
|
+
* trip refetches every page it holds in the background.
|
|
65
88
|
*/
|
|
66
89
|
export function usePagedList<T>({
|
|
67
90
|
queryKey,
|
|
@@ -70,11 +93,15 @@ export function usePagedList<T>({
|
|
|
70
93
|
pollIntervalMs,
|
|
71
94
|
pollWhile,
|
|
72
95
|
staleTime,
|
|
96
|
+
gcTime,
|
|
97
|
+
keepPreviousItems = false,
|
|
73
98
|
}: UsePagedListOptions<T>): PagedList<T> {
|
|
74
99
|
const query = useInfiniteQuery({
|
|
75
100
|
queryKey,
|
|
76
101
|
enabled,
|
|
77
102
|
staleTime,
|
|
103
|
+
gcTime,
|
|
104
|
+
placeholderData: keepPreviousItems ? keepPreviousData : undefined,
|
|
78
105
|
initialPageParam: undefined as string | undefined,
|
|
79
106
|
queryFn: ({ pageParam }) => fetchPage(pageParam),
|
|
80
107
|
getNextPageParam: (last: PagedPage<T>) => last.nextCursor,
|
|
@@ -90,7 +117,11 @@ export function usePagedList<T>({
|
|
|
90
117
|
: false,
|
|
91
118
|
});
|
|
92
119
|
|
|
93
|
-
const
|
|
120
|
+
const pages = query.data?.pages;
|
|
121
|
+
// Memoized on the query's data, which TanStack keeps referentially stable while nothing
|
|
122
|
+
// changed — flattening per render handed every consumer a new array and defeated its memos.
|
|
123
|
+
const items = useMemo(() => (pages ?? []).flatMap((p) => p.items), [pages]);
|
|
124
|
+
const total = useMemo(() => pages?.findLast((p) => p.total !== undefined)?.total, [pages]);
|
|
94
125
|
|
|
95
126
|
return {
|
|
96
127
|
items,
|
|
@@ -99,6 +130,7 @@ export function usePagedList<T>({
|
|
|
99
130
|
isError: query.isError,
|
|
100
131
|
error: query.error,
|
|
101
132
|
hasMore: !!query.hasNextPage,
|
|
133
|
+
total,
|
|
102
134
|
loadMore: () => {
|
|
103
135
|
if (query.hasNextPage && !query.isFetchingNextPage) void query.fetchNextPage();
|
|
104
136
|
},
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
16
16
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
17
17
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
18
|
-
*
|
|
18
|
+
* 713 candidate(s).
|
|
19
19
|
*
|
|
20
20
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
21
21
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
@source inline("min-h-11 min-h-[1lh] min-h-[var(--control-h-lg)] min-h-[var(--control-h-md)]");
|
|
113
113
|
@source inline("min-h-[var(--control-h-sm)] min-w-0 min-w-11 min-w-[8rem]");
|
|
114
114
|
@source inline("min-w-[var(--radix-select-trigger-width)] ml-1 ml-auto motion motion-reduce:animate-none mt-0.5");
|
|
115
|
-
@source inline("mt-1 mt-2 mx-auto my-1 object object-cover opacity opacity-0 opacity-30 opacity-40 opacity-50");
|
|
115
|
+
@source inline("mt-1 mt-2 mx-auto my my-1 object object-cover opacity opacity-0 opacity-30 opacity-40 opacity-50");
|
|
116
116
|
@source inline("opacity-60 opacity-70 out outline outline-none over overflow overflow-auto overflow-hidden");
|
|
117
117
|
@source inline("overflow-x-auto overflow-y-auto overscroll-contain p p-0 p-0.5 p-1 p-2 p-3 p-4 p-6 p-[7px]");
|
|
118
118
|
@source inline("p-[var(--panel-pad)] pause paused pb-3 pb-[8vh]");
|