najm-kit 2.7.1 → 2.7.3

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.
@@ -0,0 +1,143 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * How the page controls present position within the result.
5
+ *
6
+ * `numbered` renders a windowed list of page buttons. `compact` renders the
7
+ * `Page X of Y` text with first/previous/next/last controls.
8
+ *
9
+ * `numbered` needs a trustworthy page count. Under `manualPagination` that
10
+ * means the application must pass a `pageCount` derived from a real result
11
+ * total, or — when its endpoint reports no total — pass `hasNextPage` and no
12
+ * `pageCount` at all, which renders the unbounded bar described on the NTable
13
+ * prop. With neither, the bar falls back to `compact` on its own rather than
14
+ * inviting clicks on pages that may not exist.
15
+ *
16
+ * What it must never be handed is a `pageCount` that is really a lower bound,
17
+ * such as `pageIndex + 2`. That reads as a two-page result on page one and a
18
+ * three-page result on page two, so the bar grows a number per click with
19
+ * nothing to say why. NTable warns in development when it catches a count
20
+ * moving in lockstep with the page index.
21
+ */
22
+ type NTablePaginationVariant = "numbered" | "compact";
23
+ /**
24
+ * Accessible names and visible copy for the page controls.
25
+ *
26
+ * Every field is optional and falls back to English. Supply them to localize —
27
+ * the numbered variant is mostly digits, but its controls still need names.
28
+ */
29
+ interface NTablePaginationLabels {
30
+ /** Labels the rows-per-page select. Defaults to `"Rows/page"`. */
31
+ rowsPerPage?: string;
32
+ /** Accessible name of the whole page control group. Defaults to `"Pagination"`. */
33
+ pagination?: string;
34
+ /** Accessible name for one page button, given a 1-based page. */
35
+ goToPage?: (page: number) => string;
36
+ /** Accessible name of the current page button, given a 1-based page. */
37
+ currentPage?: (page: number) => string;
38
+ firstPage?: string;
39
+ previousPage?: string;
40
+ nextPage?: string;
41
+ lastPage?: string;
42
+ /** The `compact` variant's position text, given 1-based values. */
43
+ pageOf?: (page: number, pageCount: number) => string;
44
+ /**
45
+ * The position text when the result has no known total, given the 1-based
46
+ * page. Defaults to `"Page X"` — there is no `of Y` to state, and repeating
47
+ * the moving lower bound there would be the same lie the numbered bar avoids.
48
+ */
49
+ pageOfUnknown?: (page: number) => string;
50
+ /** The selection summary, given selected and total row counts. */
51
+ rowsSelected?: (selected: number, total: number) => string;
52
+ }
53
+ interface NTableLoadMorePagination {
54
+ /** Render the supplied rows as one card list with an explicit continuation control. */
55
+ mode: "load-more";
56
+ /** Whether the owning application has another server page available. */
57
+ hasNextPage: boolean;
58
+ /** True while the owning application is appending the next page. */
59
+ loadingMore?: boolean;
60
+ /** A controlled append error. Existing rows remain rendered and the control becomes Retry. */
61
+ loadMoreError?: ReactNode;
62
+ /** Fetch exactly one additional page. Najm Kit never constructs or owns the request. */
63
+ onLoadMore: () => unknown | Promise<unknown>;
64
+ loadMoreLabel?: string;
65
+ loadingMoreLabel?: string;
66
+ retryLabel?: string;
67
+ endLabel?: string;
68
+ loadMoreErrorLabel?: string;
69
+ /** Localize the polite announcement made after appended rows arrive. */
70
+ itemsLoadedLabel?: (count: number) => string;
71
+ }
72
+ interface NTableInfinitePagination {
73
+ /**
74
+ * Render the supplied rows as one card list that continues automatically when
75
+ * the end of the list scrolls into view. No control and no end-of-list
76
+ * element are rendered while the list is healthy; the continuation button
77
+ * appears only after an append failure, as the retry target.
78
+ */
79
+ mode: "infinite";
80
+ /** Whether the owning application has another server page available. */
81
+ hasNextPage: boolean;
82
+ /** True while the owning application is appending the next page. */
83
+ loadingMore?: boolean;
84
+ /** A controlled append error. Existing rows remain rendered and Retry appears. */
85
+ loadMoreError?: ReactNode;
86
+ /** Fetch exactly one additional page. Najm Kit never constructs or owns the request. */
87
+ onLoadMore: () => unknown | Promise<unknown>;
88
+ /**
89
+ * Distance ahead of the list end at which the next page is requested.
90
+ * Defaults to `"80px"`.
91
+ */
92
+ rootMargin?: string;
93
+ loadingMoreLabel?: string;
94
+ retryLabel?: string;
95
+ loadMoreErrorLabel?: string;
96
+ /** Localize the polite announcement made after appended rows arrive. */
97
+ itemsLoadedLabel?: (count: number) => string;
98
+ }
99
+ /**
100
+ * Presentation policy used while NTable is actually rendering cards.
101
+ *
102
+ * `paged` preserves the existing page controls. `all` renders every supplied
103
+ * row without a footer, in card and table modes alike. `load-more` renders
104
+ * every supplied row and adds a guarded, accessible continuation control.
105
+ * `infinite` renders every supplied row and continues on scroll instead.
106
+ *
107
+ * Applications remain responsible for fetching, accumulating, filtering,
108
+ * sorting, authorization, and privacy. `all` renders exactly the rows it is
109
+ * given and never fetches, so a caller that has not loaded the whole set must
110
+ * not select it.
111
+ */
112
+ type NTableCardPagination = {
113
+ mode?: "paged";
114
+ } | {
115
+ mode: "all";
116
+ } | NTableLoadMorePagination | NTableInfinitePagination;
117
+
118
+ /**
119
+ * The translator shape `NajmUIProvider` accepts.
120
+ *
121
+ * Deliberately structural rather than an import from `najm-i18n`: the kit
122
+ * depends on no `najm-*` package, and this signature is satisfied by every
123
+ * mainstream i18n library. The application keeps its catalog and its own
124
+ * language provider.
125
+ */
126
+ type NajmTranslate = (key: string, params?: Record<string, string | number>) => string;
127
+ declare const DEFAULT_PAGINATION_KEY_PREFIX = "common.pagination";
128
+ /**
129
+ * Projects a translator onto the ten pagination labels.
130
+ *
131
+ * Keys are `<prefix>.<field>`, matching the `NTablePaginationLabels` field
132
+ * names one-for-one, so a catalog is readable next to the type. Interpolation
133
+ * params are named for what they are: `page`, `pageCount`, `selected`, `total`.
134
+ *
135
+ * No result is inspected or second-guessed. A translator that echoes missing
136
+ * keys will render those keys — that is the translator's contract to define,
137
+ * and quietly swapping in English would hide the missing entry rather than
138
+ * surface it. Applications that want the packaged English for a given label
139
+ * should omit the key from the prefix and override it via `tableDefaults`.
140
+ */
141
+ declare function buildPaginationLabels(t: NajmTranslate, prefix?: string): NTablePaginationLabels;
142
+
143
+ export { DEFAULT_PAGINATION_KEY_PREFIX as D, type NajmTranslate as N, type NTableCardPagination as a, type NTablePaginationLabels as b, type NTablePaginationVariant as c, type NTableInfinitePagination as d, type NTableLoadMorePagination as e, buildPaginationLabels as f };
@@ -0,0 +1,301 @@
1
+ import * as _tanstack_query_core from '@tanstack/query-core';
2
+ import { OffsetPageFetcher, OffsetPage } from './pagination.js';
3
+ export { ApiPage, DEFAULT_MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, OffsetPageOptions, OffsetPagination, QueryValue, cleanQuery, createOffsetPagination, fetchOffsetPage, getPageIndex } from './pagination.js';
4
+ import { L as ListStrategy, R as ResolvedListMode } from './cardPagination-A6h8vXuk.js';
5
+ export { C as CardPaginationLabels, a as CardPaginationState, b as buildCardPaginationLabels, c as createCardPagination } from './cardPagination-A6h8vXuk.js';
6
+ import './paginationLabels-DgHutNWz.js';
7
+ import 'react';
8
+
9
+ interface UseOffsetInfiniteQueryOptions<T> {
10
+ enabled?: boolean;
11
+ fetchPage: OffsetPageFetcher<T>;
12
+ queryKey: readonly unknown[];
13
+ /**
14
+ * The size of one *server request*, not of a displayed page.
15
+ *
16
+ * Deliberately decoupled: the display page size is measured from the rendered
17
+ * container and moves as the viewport, column count and card height settle.
18
+ * Tying a request to it would put a network round trip behind every
19
+ * measurement correction, and a second skeleton behind each round trip.
20
+ */
21
+ windowSize?: number;
22
+ /** The server's `limit` clamp. Defaults to `DEFAULT_MAX_PAGE_SIZE`. */
23
+ maxLimit?: number;
24
+ }
25
+ declare const DEFAULT_ROW_WINDOW_SIZE = 50;
26
+ /**
27
+ * An accumulating row buffer over `fetchOffsetPage`.
28
+ *
29
+ * Every mode of `useResponsiveOffsetList` reads from one of these — numbered
30
+ * pages are a slice of the buffer rather than a request of their own.
31
+ */
32
+ declare function useOffsetInfiniteQuery<T>({ enabled, fetchPage, queryKey, windowSize, maxLimit, }: UseOffsetInfiniteQueryOptions<T>): {
33
+ rows: T[];
34
+ hasNextPage: boolean;
35
+ total: number;
36
+ data: _tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>;
37
+ error: Error;
38
+ isError: true;
39
+ isPending: false;
40
+ isLoading: false;
41
+ isLoadingError: false;
42
+ isRefetchError: true;
43
+ isSuccess: false;
44
+ isPlaceholderData: false;
45
+ status: "error";
46
+ fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
47
+ fetchPreviousPage: (options?: _tanstack_query_core.FetchPreviousPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
48
+ hasPreviousPage: boolean;
49
+ isFetchNextPageError: boolean;
50
+ isFetchingNextPage: boolean;
51
+ isFetchPreviousPageError: boolean;
52
+ isFetchingPreviousPage: boolean;
53
+ dataUpdatedAt: number;
54
+ errorUpdatedAt: number;
55
+ failureCount: number;
56
+ failureReason: Error;
57
+ errorUpdateCount: number;
58
+ isFetched: boolean;
59
+ isFetchedAfterMount: boolean;
60
+ isFetching: boolean;
61
+ isInitialLoading: boolean;
62
+ isPaused: boolean;
63
+ isRefetching: boolean;
64
+ isStale: boolean;
65
+ isEnabled: boolean;
66
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
67
+ fetchStatus: _tanstack_query_core.FetchStatus;
68
+ promise: Promise<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>>;
69
+ } | {
70
+ rows: T[];
71
+ hasNextPage: boolean;
72
+ total: number;
73
+ data: _tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>;
74
+ error: null;
75
+ isError: false;
76
+ isPending: false;
77
+ isLoading: false;
78
+ isLoadingError: false;
79
+ isRefetchError: false;
80
+ isFetchNextPageError: false;
81
+ isFetchPreviousPageError: false;
82
+ isSuccess: true;
83
+ isPlaceholderData: false;
84
+ status: "success";
85
+ fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
86
+ fetchPreviousPage: (options?: _tanstack_query_core.FetchPreviousPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
87
+ hasPreviousPage: boolean;
88
+ isFetchingNextPage: boolean;
89
+ isFetchingPreviousPage: boolean;
90
+ dataUpdatedAt: number;
91
+ errorUpdatedAt: number;
92
+ failureCount: number;
93
+ failureReason: Error;
94
+ errorUpdateCount: number;
95
+ isFetched: boolean;
96
+ isFetchedAfterMount: boolean;
97
+ isFetching: boolean;
98
+ isInitialLoading: boolean;
99
+ isPaused: boolean;
100
+ isRefetching: boolean;
101
+ isStale: boolean;
102
+ isEnabled: boolean;
103
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
104
+ fetchStatus: _tanstack_query_core.FetchStatus;
105
+ promise: Promise<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>>;
106
+ } | {
107
+ rows: T[];
108
+ hasNextPage: boolean;
109
+ total: number;
110
+ data: undefined;
111
+ error: Error;
112
+ isError: true;
113
+ isPending: false;
114
+ isLoading: false;
115
+ isLoadingError: true;
116
+ isRefetchError: false;
117
+ isFetchNextPageError: false;
118
+ isFetchPreviousPageError: false;
119
+ isSuccess: false;
120
+ isPlaceholderData: false;
121
+ status: "error";
122
+ fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
123
+ fetchPreviousPage: (options?: _tanstack_query_core.FetchPreviousPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
124
+ hasPreviousPage: boolean;
125
+ isFetchingNextPage: boolean;
126
+ isFetchingPreviousPage: boolean;
127
+ dataUpdatedAt: number;
128
+ errorUpdatedAt: number;
129
+ failureCount: number;
130
+ failureReason: Error;
131
+ errorUpdateCount: number;
132
+ isFetched: boolean;
133
+ isFetchedAfterMount: boolean;
134
+ isFetching: boolean;
135
+ isInitialLoading: boolean;
136
+ isPaused: boolean;
137
+ isRefetching: boolean;
138
+ isStale: boolean;
139
+ isEnabled: boolean;
140
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
141
+ fetchStatus: _tanstack_query_core.FetchStatus;
142
+ promise: Promise<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>>;
143
+ } | {
144
+ rows: T[];
145
+ hasNextPage: boolean;
146
+ total: number;
147
+ data: undefined;
148
+ error: null;
149
+ isError: false;
150
+ isPending: true;
151
+ isLoading: true;
152
+ isLoadingError: false;
153
+ isRefetchError: false;
154
+ isFetchNextPageError: false;
155
+ isFetchPreviousPageError: false;
156
+ isSuccess: false;
157
+ isPlaceholderData: false;
158
+ status: "pending";
159
+ fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
160
+ fetchPreviousPage: (options?: _tanstack_query_core.FetchPreviousPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
161
+ hasPreviousPage: boolean;
162
+ isFetchingNextPage: boolean;
163
+ isFetchingPreviousPage: boolean;
164
+ dataUpdatedAt: number;
165
+ errorUpdatedAt: number;
166
+ failureCount: number;
167
+ failureReason: Error;
168
+ errorUpdateCount: number;
169
+ isFetched: boolean;
170
+ isFetchedAfterMount: boolean;
171
+ isFetching: boolean;
172
+ isInitialLoading: boolean;
173
+ isPaused: boolean;
174
+ isRefetching: boolean;
175
+ isStale: boolean;
176
+ isEnabled: boolean;
177
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
178
+ fetchStatus: _tanstack_query_core.FetchStatus;
179
+ promise: Promise<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>>;
180
+ } | {
181
+ rows: T[];
182
+ hasNextPage: boolean;
183
+ total: number;
184
+ data: undefined;
185
+ error: null;
186
+ isError: false;
187
+ isPending: true;
188
+ isLoadingError: false;
189
+ isRefetchError: false;
190
+ isFetchNextPageError: false;
191
+ isFetchPreviousPageError: false;
192
+ isSuccess: false;
193
+ isPlaceholderData: false;
194
+ status: "pending";
195
+ fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
196
+ fetchPreviousPage: (options?: _tanstack_query_core.FetchPreviousPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
197
+ hasPreviousPage: boolean;
198
+ isFetchingNextPage: boolean;
199
+ isFetchingPreviousPage: boolean;
200
+ dataUpdatedAt: number;
201
+ errorUpdatedAt: number;
202
+ failureCount: number;
203
+ failureReason: Error;
204
+ errorUpdateCount: number;
205
+ isFetched: boolean;
206
+ isFetchedAfterMount: boolean;
207
+ isFetching: boolean;
208
+ isLoading: boolean;
209
+ isInitialLoading: boolean;
210
+ isPaused: boolean;
211
+ isRefetching: boolean;
212
+ isStale: boolean;
213
+ isEnabled: boolean;
214
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
215
+ fetchStatus: _tanstack_query_core.FetchStatus;
216
+ promise: Promise<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>>;
217
+ } | {
218
+ rows: T[];
219
+ hasNextPage: boolean;
220
+ total: number;
221
+ data: _tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>;
222
+ isError: false;
223
+ error: null;
224
+ isPending: false;
225
+ isLoading: false;
226
+ isLoadingError: false;
227
+ isRefetchError: false;
228
+ isSuccess: true;
229
+ isPlaceholderData: true;
230
+ isFetchNextPageError: false;
231
+ isFetchPreviousPageError: false;
232
+ status: "success";
233
+ fetchNextPage: (options?: _tanstack_query_core.FetchNextPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
234
+ fetchPreviousPage: (options?: _tanstack_query_core.FetchPreviousPageOptions) => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
235
+ hasPreviousPage: boolean;
236
+ isFetchingNextPage: boolean;
237
+ isFetchingPreviousPage: boolean;
238
+ dataUpdatedAt: number;
239
+ errorUpdatedAt: number;
240
+ failureCount: number;
241
+ failureReason: Error;
242
+ errorUpdateCount: number;
243
+ isFetched: boolean;
244
+ isFetchedAfterMount: boolean;
245
+ isFetching: boolean;
246
+ isInitialLoading: boolean;
247
+ isPaused: boolean;
248
+ isRefetching: boolean;
249
+ isStale: boolean;
250
+ isEnabled: boolean;
251
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
252
+ fetchStatus: _tanstack_query_core.FetchStatus;
253
+ promise: Promise<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>>;
254
+ };
255
+
256
+ interface PaginationState {
257
+ pageIndex: number;
258
+ pageSize: number;
259
+ }
260
+ type PaginationUpdater = PaginationState | ((current: PaginationState) => PaginationState);
261
+ interface UseResponsiveOffsetListOptions<T> {
262
+ enabled?: boolean;
263
+ fetchPage: OffsetPageFetcher<T>;
264
+ pageSize?: number;
265
+ queryKey: readonly unknown[];
266
+ /** Defaults to `"paged"`. See `ListStrategy`. */
267
+ strategy?: ListStrategy;
268
+ /** The server's `limit` clamp. Defaults to `DEFAULT_MAX_PAGE_SIZE`. */
269
+ maxLimit?: number;
270
+ /** Below this width the list continues on scroll. Defaults to `lg`. */
271
+ cardBreakpoint?: number;
272
+ }
273
+ /**
274
+ * One offset-paginated list, in whichever presentation the viewport calls for.
275
+ *
276
+ * Feeds `NTable` directly: the return value carries `pagination`,
277
+ * `onPaginationChange` and `pageCount` for the page controls, and the fields
278
+ * `createCardPagination` reads for scroll continuation.
279
+ */
280
+ declare function useResponsiveOffsetList<T>({ enabled, fetchPage, pageSize, queryKey, strategy, maxLimit, cardBreakpoint, }: UseResponsiveOffsetListOptions<T>): {
281
+ cardViewport: boolean;
282
+ mode: ResolvedListMode;
283
+ data: T[];
284
+ /** Rows matching the current filters on the server, or `null` if unknown. */
285
+ total: number;
286
+ error: Error;
287
+ hasNextPage: boolean;
288
+ loading: boolean;
289
+ loadingMore: boolean;
290
+ loadMoreError: Error;
291
+ onLoadMore: () => Promise<_tanstack_query_core.InfiniteQueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
292
+ onPaginationChange: (updater: PaginationUpdater) => void;
293
+ pageCount: number;
294
+ pagination: {
295
+ pageIndex: number;
296
+ pageSize: number;
297
+ };
298
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<OffsetPage<T>, unknown>, Error>>;
299
+ };
300
+
301
+ export { DEFAULT_ROW_WINDOW_SIZE, ListStrategy, OffsetPage, OffsetPageFetcher, ResolvedListMode, type UseOffsetInfiniteQueryOptions, type UseResponsiveOffsetListOptions, useOffsetInfiniteQuery, useResponsiveOffsetList };
package/dist/query.mjs ADDED
@@ -0,0 +1,133 @@
1
+ import { useCardViewport } from './chunk-XGDMPI5U.mjs';
2
+ export { buildCardPaginationLabels, createCardPagination } from './chunk-XGDMPI5U.mjs';
3
+ import { fetchOffsetPage, DEFAULT_MAX_PAGE_SIZE } from './chunk-2NX2VKS2.mjs';
4
+ export { DEFAULT_MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, cleanQuery, createOffsetPagination, fetchOffsetPage, getPageIndex } from './chunk-2NX2VKS2.mjs';
5
+ import { useInfiniteQuery } from '@tanstack/react-query';
6
+ import { useState, useRef, useEffect, useCallback } from 'react';
7
+
8
+ var DEFAULT_ROW_WINDOW_SIZE = 50;
9
+ function useOffsetInfiniteQuery({
10
+ enabled = true,
11
+ fetchPage,
12
+ queryKey,
13
+ windowSize = DEFAULT_ROW_WINDOW_SIZE,
14
+ maxLimit
15
+ }) {
16
+ const query = useInfiniteQuery({
17
+ enabled,
18
+ initialPageParam: 0,
19
+ queryKey: [...queryKey, "buffer", windowSize],
20
+ queryFn: ({ pageParam }) => fetchOffsetPage(
21
+ fetchPage,
22
+ { limit: windowSize, offset: Number(pageParam) },
23
+ { maxLimit }
24
+ ),
25
+ getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextOffset : void 0
26
+ });
27
+ const pages = query.data?.pages;
28
+ return {
29
+ ...query,
30
+ rows: pages?.flatMap((page) => page.rows) ?? [],
31
+ hasNextPage: Boolean(query.hasNextPage),
32
+ // The newest window carries the freshest count. Reading the first page
33
+ // instead would keep reporting a total from before the latest mutation.
34
+ total: pages?.[pages.length - 1]?.total ?? null
35
+ };
36
+ }
37
+ function useResponsiveOffsetList({
38
+ enabled = true,
39
+ fetchPage,
40
+ pageSize = 25,
41
+ queryKey,
42
+ strategy = "paged",
43
+ maxLimit = DEFAULT_MAX_PAGE_SIZE,
44
+ cardBreakpoint
45
+ }) {
46
+ const cardViewport = useCardViewport(cardBreakpoint);
47
+ const queryIdentity = JSON.stringify(queryKey);
48
+ const [paginationState, setPagination] = useState({
49
+ pageIndex: 0,
50
+ pageSize,
51
+ queryIdentity
52
+ });
53
+ const pagination = paginationState.queryIdentity === queryIdentity ? paginationState : { ...paginationState, pageIndex: 0};
54
+ const wantsAll = strategy === "all";
55
+ const wantsInfinite = strategy === "infinite" || strategy === "paged" && cardViewport;
56
+ const buffer = useOffsetInfiniteQuery({
57
+ enabled,
58
+ fetchPage,
59
+ queryKey,
60
+ maxLimit,
61
+ windowSize: wantsAll ? maxLimit : DEFAULT_ROW_WINDOW_SIZE
62
+ });
63
+ const rows = buffer.rows;
64
+ const allDowngraded = wantsAll && buffer.hasNextPage;
65
+ const warnedRef = useRef(false);
66
+ useEffect(() => {
67
+ if (!allDowngraded || warnedRef.current) return;
68
+ warnedRef.current = true;
69
+ if (process.env.NODE_ENV !== "production") {
70
+ console.warn(
71
+ `[najm-kit] List ${queryIdentity} requested strategy "all" but filled the ${maxLimit}-row ceiling, so its bound is not real. Falling back to infinite continuation; give this list a paged or infinite strategy.`
72
+ );
73
+ }
74
+ }, [allDowngraded, queryIdentity, maxLimit]);
75
+ const mode = wantsAll && !allDowngraded ? "all" : wantsInfinite || allDowngraded ? "infinite" : "paged";
76
+ const start = pagination.pageIndex * pagination.pageSize;
77
+ const data = mode === "paged" ? rows.slice(start, start + pagination.pageSize) : rows;
78
+ const { fetchNextPage, hasNextPage, isFetchingNextPage } = buffer;
79
+ useEffect(() => {
80
+ if (mode !== "paged") return;
81
+ if (!hasNextPage || isFetchingNextPage) return;
82
+ if (rows.length >= start + pagination.pageSize * 2) return;
83
+ void fetchNextPage();
84
+ }, [
85
+ mode,
86
+ hasNextPage,
87
+ isFetchingNextPage,
88
+ rows.length,
89
+ start,
90
+ pagination.pageSize,
91
+ fetchNextPage
92
+ ]);
93
+ const onPaginationChange = useCallback(
94
+ (updater) => {
95
+ setPagination((current) => ({
96
+ ...typeof updater === "function" ? updater(
97
+ current.queryIdentity === queryIdentity ? { pageIndex: current.pageIndex, pageSize: current.pageSize } : { pageIndex: 0, pageSize: current.pageSize }
98
+ ) : updater,
99
+ queryIdentity
100
+ }));
101
+ },
102
+ [queryIdentity]
103
+ );
104
+ const bufferedPages = Math.max(
105
+ 1,
106
+ Math.ceil(rows.length / pagination.pageSize)
107
+ );
108
+ const pageCount = buffer.total !== null ? Math.max(1, Math.ceil(buffer.total / pagination.pageSize)) : buffer.hasNextPage ? bufferedPages + 1 : bufferedPages;
109
+ return {
110
+ cardViewport,
111
+ mode,
112
+ data,
113
+ /** Rows matching the current filters on the server, or `null` if unknown. */
114
+ total: buffer.total,
115
+ error: buffer.error,
116
+ hasNextPage: mode === "infinite" && buffer.hasNextPage,
117
+ // A background window extension is not a load. Paged readers keep the rows
118
+ // they are looking at; only having nothing at all is a loading state.
119
+ loading: buffer.isPending,
120
+ loadingMore: mode === "infinite" && buffer.isFetchingNextPage,
121
+ loadMoreError: buffer.isFetchNextPageError ? buffer.error : null,
122
+ onLoadMore: () => buffer.fetchNextPage(),
123
+ onPaginationChange,
124
+ pageCount,
125
+ pagination: {
126
+ pageIndex: pagination.pageIndex,
127
+ pageSize: pagination.pageSize
128
+ },
129
+ refetch: buffer.refetch
130
+ };
131
+ }
132
+
133
+ export { DEFAULT_ROW_WINDOW_SIZE, useOffsetInfiniteQuery, useResponsiveOffsetList };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Reusable React UI component package for Najm framework",
@@ -33,6 +33,21 @@
33
33
  "default": "./dist/adapters/app.mjs"
34
34
  },
35
35
  "./package.json": "./package.json",
36
+ "./query": {
37
+ "types": "./dist/query.d.ts",
38
+ "import": "./dist/query.mjs",
39
+ "default": "./dist/query.mjs"
40
+ },
41
+ "./format": {
42
+ "types": "./dist/format.d.ts",
43
+ "import": "./dist/format.mjs",
44
+ "default": "./dist/format.mjs"
45
+ },
46
+ "./pagination": {
47
+ "types": "./dist/pagination.d.ts",
48
+ "import": "./dist/pagination.mjs",
49
+ "default": "./dist/pagination.mjs"
50
+ },
36
51
  "./json": {
37
52
  "types": "./dist/json.d.ts",
38
53
  "import": "./dist/json.mjs",
@@ -60,6 +75,7 @@
60
75
  "zod": ">=4",
61
76
  "next": ">=14",
62
77
  "najm-i18n": ">=2",
78
+ "@tanstack/react-query": "^5",
63
79
  "@uiw/react-codemirror": "^4.25.0",
64
80
  "@codemirror/state": "^6.6.0",
65
81
  "@codemirror/view": "^6.42.0",
@@ -76,6 +92,9 @@
76
92
  "najm-i18n": {
77
93
  "optional": true
78
94
  },
95
+ "@tanstack/react-query": {
96
+ "optional": true
97
+ },
79
98
  "@uiw/react-codemirror": {
80
99
  "optional": true
81
100
  },
@@ -147,6 +166,7 @@
147
166
  "@hookform/resolvers": "^5",
148
167
  "@lezer/highlight": "^1.2.0",
149
168
  "@tailwindcss/postcss": "^4",
169
+ "@tanstack/react-query": "^5",
150
170
  "@testing-library/react": "^16",
151
171
  "@testing-library/user-event": "^14.6.1",
152
172
  "@types/culori": "^4.0.1",