akanjs 3.0.0-alpha.96 → 3.0.0-alpha.97

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.
@@ -78,6 +78,8 @@ export class SliceInitHandle {
78
78
  [`pageOf${capRefName}`]: page,
79
79
  [`lastPageOf${capRefName}`]: lastPage,
80
80
  [`limitOf${capRefName}`]: limit,
81
+
82
+ [`hasMoreOf${capRefName}`]: !!limit && modelObjList.length >= limit,
81
83
  [`queryArgsOf${capRefName}`]: queryArgs,
82
84
  [`sortOf${capRefName}`]: sort,
83
85
  [`${refName}InitAt`]: new Date(),
@@ -43,6 +43,8 @@ type ServerInitShape<
43
43
  [K in `lastPageOf${CapRefName}`]: number;
44
44
  } & {
45
45
  [K in `limitOf${CapRefName}`]: number;
46
+ } & {
47
+ [K in `hasMoreOf${CapRefName}`]: boolean;
46
48
  } & {
47
49
  [K in `queryArgsOf${CapRefName}`]: QueryArgs;
48
50
  } & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.96",
3
+ "version": "3.0.0-alpha.97",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/store/action.ts CHANGED
@@ -177,7 +177,7 @@ export type SliceAction<
177
177
  } & {
178
178
  [K in `setPageOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (page: number, options?: FetchPolicy) => Promise<void>;
179
179
  } & {
180
- [K in `addPageOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (page: number, options?: FetchPolicy) => Promise<void>;
180
+ [K in `loadMoreOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (options?: FetchPolicy) => Promise<void>;
181
181
  } & {
182
182
  [K in `setLimitOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (
183
183
  limit: number,
@@ -273,11 +273,14 @@ type DefaultSliceActionFields<
273
273
  } & {
274
274
  [Suffix in _Suffixes as
275
275
  | `setPageOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`
276
- | `addPageOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`
277
276
  | `setLimitOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (
278
277
  value: number,
279
278
  options?: FetchPolicy,
280
279
  ) => Promise<void>;
280
+ } & {
281
+ [Suffix in _Suffixes as `loadMoreOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (
282
+ options?: FetchPolicy,
283
+ ) => Promise<void>;
281
284
  } & {
282
285
  [Suffix in _Suffixes as `setQueryArgsOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (
283
286
  ...args:
@@ -528,7 +531,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
528
531
  refreshModel: `refresh${className}`,
529
532
  selectModel: `select${className}`,
530
533
  setPageOfModel: `setPageOf${className}`,
531
- addPageOfModel: `addPageOf${className}`,
534
+ loadMoreOfModel: `loadMoreOf${className}`,
532
535
  setLimitOfModel: `setLimitOf${className}`,
533
536
  setQueryArgsOfModel: `setQueryArgsOf${className}`,
534
537
  setSortOfModel: `setSortOf${className}`,
@@ -537,6 +540,8 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
537
540
  lastPageOfModel: `lastPageOf${className}`,
538
541
  pageOfModel: `pageOf${className}`,
539
542
  limitOfModel: `limitOf${className}`,
543
+ hasMoreOfModel: `hasMoreOf${className}`,
544
+ isCumulativeOfModel: `isCumulativeOf${className}`,
540
545
  queryArgsOfModel: `queryArgsOf${className}`,
541
546
  sortOfModel: `sortOf${className}`,
542
547
  };
@@ -1034,7 +1039,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1034
1039
  refreshModel: SliceName.replace(names.Model, names.refreshModel),
1035
1040
  selectModel: SliceName.replace(names.Model, names.selectModel),
1036
1041
  setPageOfModel: SliceName.replace(names.Model, names.setPageOfModel),
1037
- addPageOfModel: SliceName.replace(names.Model, names.addPageOfModel),
1042
+ loadMoreOfModel: SliceName.replace(names.Model, names.loadMoreOfModel),
1038
1043
  setLimitOfModel: SliceName.replace(names.Model, names.setLimitOfModel),
1039
1044
  setQueryArgsOfModel: SliceName.replace(names.Model, names.setQueryArgsOfModel),
1040
1045
  setSortOfModel: SliceName.replace(names.Model, names.setSortOfModel),
@@ -1043,10 +1048,23 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1043
1048
  lastPageOfModel: SliceName.replace(names.Model, names.lastPageOfModel),
1044
1049
  pageOfModel: SliceName.replace(names.Model, names.pageOfModel),
1045
1050
  limitOfModel: SliceName.replace(names.Model, names.limitOfModel),
1051
+ hasMoreOfModel: SliceName.replace(names.Model, names.hasMoreOfModel),
1052
+ isCumulativeOfModel: SliceName.replace(names.Model, names.isCumulativeOfModel),
1046
1053
  queryArgsOfModel: SliceName.replace(names.Model, names.queryArgsOfModel),
1047
1054
  sortOfModel: SliceName.replace(names.Model, names.sortOfModel),
1048
1055
  modelSelection: SliceName.replace(names.Model, names.modelSelection),
1049
1056
  };
1057
+ /**
1058
+ * Whether the server still holds rows past the ones just returned.
1059
+ *
1060
+ * Read off the batch rather than off `<model>Insight`, because the count is a second query — absent entirely
1061
+ * under `{ insight: false }`, and one live event out of step with the list the rest of the time. A short
1062
+ * batch is the server saying it had nothing more; a full one costs at most one extra request that comes back
1063
+ * empty when the total happens to be a multiple of the limit.
1064
+ */
1065
+ const hasMoreFrom = (batch: unknown[], askedFor: number) => ({
1066
+ [namesOfSlice.hasMoreOfModel]: batch.length >= askedFor && askedFor > 0,
1067
+ });
1050
1068
  const singleSliceAction = {
1051
1069
  [namesOfSlice.initModel]: async function (
1052
1070
  this: SetGet,
@@ -1057,7 +1075,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1057
1075
  FetchPolicy;
1058
1076
  const queryArgs = new Array(initArgLength).fill(null).map((_, i) => args[i] as object);
1059
1077
  const defaultModel = new cnst.full().set(initForm.default ?? {}) as unknown as Full;
1060
- this.set({ [names.defaultModel]: defaultModel });
1078
+ this.set({ [names.defaultModel]: defaultModel, [namesOfSlice.isCumulativeOfModel]: false });
1061
1079
  await ((this as unknown as DynamicRecord)[namesOfSlice.refreshModel] as (...args: any[]) => Promise<void>)({
1062
1080
  ...initForm,
1063
1081
  queryArgs,
@@ -1089,6 +1107,10 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1089
1107
  ...fetchPolicy
1090
1108
  } = initForm;
1091
1109
  const modelOperation = currentState[names.modelOperation] as string;
1110
+ const isCumulative = currentState[namesOfSlice.isCumulativeOfModel] as boolean;
1111
+ const loadedLength = (currentState[namesOfSlice.modelList] as DataList<Light>).length;
1112
+
1113
+ const fetchLimit = isCumulative ? Math.max(limit, loadedLength) : limit;
1092
1114
  const queryArgsOfModel = currentState[namesOfSlice.queryArgsOfModel] as object[];
1093
1115
  const pageOfModel = currentState[namesOfSlice.pageOfModel] as number;
1094
1116
  const limitOfModel = currentState[namesOfSlice.limitOfModel] as number;
@@ -1112,7 +1134,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1112
1134
  (fetch[namesOfSlice.modelList] as (...args: any[]) => Promise<Light[]>)(
1113
1135
  ...fetchQueryArgs,
1114
1136
  (page - 1) * limit,
1115
- limit,
1137
+ fetchLimit,
1116
1138
  sort,
1117
1139
  { ...fetchPolicy, onError: initForm.onError },
1118
1140
  ),
@@ -1134,6 +1156,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1134
1156
  [namesOfSlice.modelInitList]: modelList,
1135
1157
  [namesOfSlice.modelInitAt]: new Date(),
1136
1158
  [namesOfSlice.lastPageOfModel]: Math.max(Math.floor((modelInsight.count - 1) / (limit || 20)) + 1, 1),
1159
+ ...hasMoreFrom(modelDataList, fetchLimit),
1137
1160
  [namesOfSlice.limitOfModel]: limit,
1138
1161
  [namesOfSlice.queryArgsOfModel]: queryArgs,
1139
1162
  [namesOfSlice.sortOfModel]: sort,
@@ -1183,35 +1206,51 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1183
1206
  this.set({
1184
1207
  [namesOfSlice.modelList]: new DataList(modelDataList),
1185
1208
  [namesOfSlice.pageOfModel]: page,
1209
+ [namesOfSlice.isCumulativeOfModel]: false,
1210
+ ...hasMoreFrom(modelDataList, limitOfModel),
1186
1211
  });
1187
1212
  } finally {
1188
1213
  if (requests.isCurrent(ticket)) this.set({ [namesOfSlice.modelListLoading]: false });
1189
1214
  }
1190
1215
  },
1191
- [namesOfSlice.addPageOfModel]: async function (this: SetGet, page: number, options?: FetchPolicy) {
1216
+ /**
1217
+ * Appends the rows after the ones already loaded, turning this slice's window into a cumulative list.
1218
+ *
1219
+ * The offset is the list on screen rather than a page number, and that is the whole point. A page number
1220
+ * assumes the server's ordering baseline has not moved, which a live insertion at the front breaks on the
1221
+ * first event; the rows in hand are the first `n` the query returns whatever has been inserted since, so
1222
+ * asking for what comes after them cannot drift from what is displayed. It also leaves `pageOf<Model>` at
1223
+ * 1, which is what keeps live placement — refused anywhere but the first page — working past the first
1224
+ * "more".
1225
+ */
1226
+ [namesOfSlice.loadMoreOfModel]: async function (this: SetGet, options?: FetchPolicy) {
1192
1227
  const currentState = this.get() as { [key: string]: any };
1228
+
1229
+ if (currentState[namesOfSlice.modelListLoading] as boolean) return;
1230
+ if (!(currentState[namesOfSlice.hasMoreOfModel] as boolean)) return;
1193
1231
  const modelList = currentState[namesOfSlice.modelList] as DataList<Light>;
1194
1232
  const queryArgsOfModel = currentState[namesOfSlice.queryArgsOfModel] as object[];
1195
1233
  const pageOfModel = currentState[namesOfSlice.pageOfModel] as number;
1196
- const limitOfModel = currentState[namesOfSlice.limitOfModel] as number;
1234
+ const limitOfModel = (currentState[namesOfSlice.limitOfModel] as number) || 20;
1197
1235
  const sortOfModel = currentState[namesOfSlice.sortOfModel] as Sort;
1198
- if (pageOfModel === page) return;
1199
- const addFront = page < pageOfModel;
1200
1236
  const ticket = requests.claim();
1201
1237
  const fetchQueryArgs = expandQueryArgs(queryArgsOfModel, slice.args);
1202
1238
  const modelDataList = await (fetch[namesOfSlice.modelList] as (...args: any[]) => Promise<Light[]>)(
1203
1239
  ...fetchQueryArgs,
1204
- (page - 1) * limitOfModel,
1240
+ (pageOfModel - 1) * limitOfModel + modelList.length,
1205
1241
  limitOfModel,
1206
1242
  sortOfModel,
1207
1243
  options,
1208
1244
  );
1209
1245
 
1210
1246
  if (!requests.isCurrent(ticket)) return;
1211
- const newModelList = new DataList(
1212
- addFront ? [...modelDataList, ...modelList] : [...modelList, ...modelDataList],
1213
- );
1214
- this.set({ [namesOfSlice.modelList]: newModelList, [namesOfSlice.pageOfModel]: page });
1247
+
1248
+ const currentModelList = (this.get() as { [key: string]: any })[namesOfSlice.modelList] as DataList<Light>;
1249
+ this.set({
1250
+ [namesOfSlice.modelList]: new DataList([...currentModelList.values, ...modelDataList]),
1251
+ [namesOfSlice.isCumulativeOfModel]: true,
1252
+ ...hasMoreFrom(modelDataList, limitOfModel),
1253
+ });
1215
1254
  },
1216
1255
  [namesOfSlice.setLimitOfModel]: async function (this: SetGet, limit: number, options?: FetchPolicy) {
1217
1256
  const currentState = this.get() as { [key: string]: any };
@@ -1240,6 +1279,8 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1240
1279
  [namesOfSlice.lastPageOfModel]: Math.max(Math.floor((modelInsight.count - 1) / limit) + 1, 1),
1241
1280
  [namesOfSlice.limitOfModel]: limit,
1242
1281
  [namesOfSlice.pageOfModel]: page,
1282
+ [namesOfSlice.isCumulativeOfModel]: false,
1283
+ ...hasMoreFrom(modelDataList, limit),
1243
1284
  });
1244
1285
  } finally {
1245
1286
  if (requests.isCurrent(ticket)) this.set({ [namesOfSlice.modelListLoading]: false });
@@ -1291,6 +1332,8 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1291
1332
  [namesOfSlice.modelInsight]: modelInsight,
1292
1333
  [namesOfSlice.lastPageOfModel]: Math.max(Math.floor((modelInsight.count - 1) / limitOfModel) + 1, 1),
1293
1334
  [namesOfSlice.pageOfModel]: 1,
1335
+ [namesOfSlice.isCumulativeOfModel]: false,
1336
+ ...hasMoreFrom(modelDataList, limitOfModel),
1294
1337
  [namesOfSlice.modelSelection]: new Map(),
1295
1338
  });
1296
1339
  } finally {
@@ -1319,6 +1362,8 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1319
1362
  [namesOfSlice.modelList]: new DataList(modelDataList),
1320
1363
  [namesOfSlice.sortOfModel]: sort,
1321
1364
  [namesOfSlice.pageOfModel]: 1,
1365
+ [namesOfSlice.isCumulativeOfModel]: false,
1366
+ ...hasMoreFrom(modelDataList, limitOfModel),
1322
1367
  });
1323
1368
  } finally {
1324
1369
  if (requests.isCurrent(ticket)) this.set({ [namesOfSlice.modelListLoading]: false });
@@ -1329,7 +1374,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1329
1374
  *
1330
1375
  * The verb already answers membership — the server evaluated this slice's own query against the document
1331
1376
  * before and after the write, so arriving here at all is the proof, and nothing is re-checked. What is left
1332
- * is the window: `<slice>List` is one page of the list, not the list, so an insertion is only ever attempted
1377
+ * is the window: `<slice>List` is a prefix of the list, not the list, so an insertion is only ever attempted
1333
1378
  * where its position can be known. Everywhere else the list is stamped stale and refetched, which is always
1334
1379
  * correct and merely costs a round trip.
1335
1380
  */
@@ -1344,6 +1389,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1344
1389
  }
1345
1390
  const modelInsight = currentState[namesOfSlice.modelInsight] as Insight & BaseInsight;
1346
1391
  const limit = (currentState[namesOfSlice.limitOfModel] as number) || 20;
1392
+ const isCumulative = currentState[namesOfSlice.isCumulativeOfModel] as boolean;
1347
1393
  const countedTo = (count: number) => ({
1348
1394
  [namesOfSlice.modelInsight]: new cnst.insight().set({ ...modelInsight, count }),
1349
1395
  [namesOfSlice.lastPageOfModel]: Math.max(Math.floor((count - 1) / limit) + 1, 1),
@@ -1372,6 +1418,8 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1372
1418
  row: light as unknown as LiveSortableRow,
1373
1419
  page: currentState[namesOfSlice.pageOfModel] as number,
1374
1420
  limit,
1421
+ cumulative: isCumulative,
1422
+ hasMore: currentState[namesOfSlice.hasMoreOfModel] as boolean,
1375
1423
  sortKey: String(currentState[namesOfSlice.sortOfModel]),
1376
1424
  allowedSorts: slice.live?.sort ?? [],
1377
1425
  sorts: fetch.sortValueMap?.get(refName),
@@ -1382,7 +1430,8 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
1382
1430
  return;
1383
1431
  }
1384
1432
 
1385
- const placed = [...modelList.slice(0, placement), light, ...modelList.slice(placement)].slice(0, limit);
1433
+ const inserted = [...modelList.slice(0, placement), light, ...modelList.slice(placement)];
1434
+ const placed = isCumulative ? inserted : inserted.slice(0, limit);
1386
1435
  this.set({
1387
1436
  [namesOfSlice.modelList]: new DataList(placed as Light[]),
1388
1437
  ...countedTo(modelInsight.count + 1),
@@ -8,6 +8,10 @@ export interface LivePlacementProps {
8
8
  /** Which page of the list is on screen. Only the first can place a row. */
9
9
  page: number;
10
10
  limit: number;
11
+ /** Whether the list is pages `1..N` concatenated rather than one window. */
12
+ cumulative: boolean;
13
+ /** Whether the server still holds rows past the ones in hand. */
14
+ hasMore: boolean;
11
15
  /** The sort key the window is ordered by. */
12
16
  sortKey: string;
13
17
  /** The sort keys the slice declared a subscriber may reproduce. */
@@ -38,6 +42,8 @@ export const livePlacementIndex = ({
38
42
  row,
39
43
  page,
40
44
  limit,
45
+ cumulative,
46
+ hasMore,
41
47
  sortKey,
42
48
  allowedSorts,
43
49
  sorts,
@@ -49,9 +55,10 @@ export const livePlacementIndex = ({
49
55
  const paths = Object.entries(sort);
50
56
  if (paths.some(([path]) => comparableOf(row[path]) === null)) return null;
51
57
  const index = list.findIndex((item) => compareRows(row, item, paths) < 0);
58
+ if (index !== -1) return index;
52
59
 
53
- if (index === -1) return list.length < limit ? list.length : null;
54
- return index;
60
+ if (cumulative) return hasMore ? null : list.length;
61
+ return list.length < limit ? list.length : null;
55
62
  };
56
63
 
57
64
  const compareRows = (left: LiveSortableRow, right: LiveSortableRow, paths: [string, 1 | -1][]): number => {
@@ -7,7 +7,7 @@ export type SliceActionKey =
7
7
  | "refreshModel"
8
8
  | "selectModel"
9
9
  | "setPageOfModel"
10
- | "addPageOfModel"
10
+ | "loadMoreOfModel"
11
11
  | "setLimitOfModel"
12
12
  | "setQueryArgsOfModel"
13
13
  | "setSortOfModel"
package/store/state.ts CHANGED
@@ -49,6 +49,8 @@ export type SliceStateKey =
49
49
  | "lastPageOfModel"
50
50
  | "pageOfModel"
51
51
  | "limitOfModel"
52
+ | "hasMoreOfModel"
53
+ | "isCumulativeOfModel"
52
54
  | "queryArgsOfModel"
53
55
  | "sortOfModel";
54
56
  type _SliceMap<S extends SliceCls> = StoreSliceMap<S>;
@@ -114,6 +116,10 @@ export type SliceState<
114
116
  [K in `pageOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: number;
115
117
  } & {
116
118
  [K in `limitOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: number;
119
+ } & {
120
+ [K in
121
+ | `hasMoreOf${_CapitalizedRefName}${_CapitalizedSuffix}`
122
+ | `isCumulativeOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: boolean;
117
123
  } & {
118
124
  [K in `queryArgsOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: Args;
119
125
  } & {
@@ -150,6 +156,10 @@ type DefaultSliceStateFields<
150
156
  | `lastPageOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`
151
157
  | `pageOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`
152
158
  | `limitOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: number;
159
+ } & {
160
+ [Suffix in _Suffixes as
161
+ | `hasMoreOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`
162
+ | `isCumulativeOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: boolean;
153
163
  } & {
154
164
  [Suffix in _Suffixes as `queryArgsOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: StoreSliceArgs<
155
165
  SlceCls,
@@ -206,6 +216,8 @@ export const createSliceState = (refName: string, slice: { [key: string]: Serial
206
216
  lastPageOfModel: `lastPageOf${className}`,
207
217
  pageOfModel: `pageOf${className}`,
208
218
  limitOfModel: `limitOf${className}`,
219
+ hasMoreOfModel: `hasMoreOf${className}`,
220
+ isCumulativeOfModel: `isCumulativeOf${className}`,
209
221
  queryArgsOfModel: `queryArgsOf${className}`,
210
222
  sortOfModel: `sortOf${className}`,
211
223
  };
@@ -225,6 +237,8 @@ export const createSliceState = (refName: string, slice: { [key: string]: Serial
225
237
  lastPageOfModel: SliceName.replace(names.Model, names.lastPageOfModel),
226
238
  pageOfModel: SliceName.replace(names.Model, names.pageOfModel),
227
239
  limitOfModel: SliceName.replace(names.Model, names.limitOfModel),
240
+ hasMoreOfModel: SliceName.replace(names.Model, names.hasMoreOfModel),
241
+ isCumulativeOfModel: SliceName.replace(names.Model, names.isCumulativeOfModel),
228
242
  queryArgsOfModel: SliceName.replace(names.Model, names.queryArgsOfModel),
229
243
  sortOfModel: SliceName.replace(names.Model, names.sortOfModel),
230
244
  };
@@ -240,6 +254,8 @@ export const createSliceState = (refName: string, slice: { [key: string]: Serial
240
254
  [namesOfSlice.lastPageOfModel]: 1,
241
255
  [namesOfSlice.pageOfModel]: 1,
242
256
  [namesOfSlice.limitOfModel]: 20,
257
+ [namesOfSlice.hasMoreOfModel]: false,
258
+ [namesOfSlice.isCumulativeOfModel]: false,
243
259
  [namesOfSlice.queryArgsOfModel]: [],
244
260
  [namesOfSlice.sortOfModel]: "latest",
245
261
  };
@@ -433,6 +433,8 @@ export class StoreInstance {
433
433
  modelStaleAt: `${fieldName}StaleAt`,
434
434
  pageOfModel: `pageOf${className}`,
435
435
  limitOfModel: `limitOf${className}`,
436
+ hasMoreOfModel: `hasMoreOf${className}`,
437
+ isCumulativeOfModel: `isCumulativeOf${className}`,
436
438
  queryArgsOfModel: `queryArgsOf${className}`,
437
439
  sortOfModel: `sortOf${className}`,
438
440
  modelSelection: `${fieldName}Selection`,
@@ -440,7 +442,7 @@ export class StoreInstance {
440
442
  refreshModel: `refresh${className}`,
441
443
  selectModel: `select${className}`,
442
444
  setPageOfModel: `setPageOf${className}`,
443
- addPageOfModel: `addPageOf${className}`,
445
+ loadMoreOfModel: `loadMoreOf${className}`,
444
446
  setLimitOfModel: `setLimitOf${className}`,
445
447
  setQueryArgsOfModel: `setQueryArgsOf${className}`,
446
448
  setSortOfModel: `setSortOf${className}`,
@@ -460,6 +462,8 @@ export class StoreInstance {
460
462
  lastPageOfModel: SliceName.replace(names.Model, names.lastPageOfModel),
461
463
  pageOfModel: SliceName.replace(names.Model, names.pageOfModel),
462
464
  limitOfModel: SliceName.replace(names.Model, names.limitOfModel),
465
+ hasMoreOfModel: SliceName.replace(names.Model, names.hasMoreOfModel),
466
+ isCumulativeOfModel: SliceName.replace(names.Model, names.isCumulativeOfModel),
463
467
  queryArgsOfModel: SliceName.replace(names.Model, names.queryArgsOfModel),
464
468
  sortOfModel: SliceName.replace(names.Model, names.sortOfModel),
465
469
  modelSelection: SliceName.replace(names.Model, names.modelSelection),
@@ -469,7 +473,7 @@ export class StoreInstance {
469
473
  refreshModel: SliceName.replace(names.Model, names.refreshModel),
470
474
  selectModel: SliceName.replace(names.Model, names.selectModel),
471
475
  setPageOfModel: SliceName.replace(names.Model, names.setPageOfModel),
472
- addPageOfModel: SliceName.replace(names.Model, names.addPageOfModel),
476
+ loadMoreOfModel: SliceName.replace(names.Model, names.loadMoreOfModel),
473
477
  setLimitOfModel: SliceName.replace(names.Model, names.setLimitOfModel),
474
478
  setQueryArgsOfModel: SliceName.replace(names.Model, names.setQueryArgsOfModel),
475
479
  setSortOfModel: SliceName.replace(names.Model, names.setSortOfModel),
@@ -32,6 +32,8 @@ type ServerInitShape<RefName extends string, QueryArgs, CapRefName extends strin
32
32
  [K in `lastPageOf${CapRefName}`]: number;
33
33
  } & {
34
34
  [K in `limitOf${CapRefName}`]: number;
35
+ } & {
36
+ [K in `hasMoreOf${CapRefName}`]: boolean;
35
37
  } & {
36
38
  [K in `queryArgsOf${CapRefName}`]: QueryArgs;
37
39
  } & {
@@ -100,7 +100,7 @@ export type SliceAction<RefName extends string, Suffix extends string, Input, Li
100
100
  } & {
101
101
  [K in `setPageOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (page: number, options?: FetchPolicy) => Promise<void>;
102
102
  } & {
103
- [K in `addPageOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (page: number, options?: FetchPolicy) => Promise<void>;
103
+ [K in `loadMoreOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (options?: FetchPolicy) => Promise<void>;
104
104
  } & {
105
105
  [K in `setLimitOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: (limit: number, options?: FetchPolicy) => Promise<void>;
106
106
  } & {
@@ -143,7 +143,9 @@ type DefaultSliceActionFields<SlceCls extends SliceCls, _CapRef extends string,
143
143
  remove?: boolean;
144
144
  }) => void;
145
145
  } & {
146
- [Suffix in _Suffixes as `setPageOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}` | `addPageOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}` | `setLimitOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (value: number, options?: FetchPolicy) => Promise<void>;
146
+ [Suffix in _Suffixes as `setPageOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}` | `setLimitOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (value: number, options?: FetchPolicy) => Promise<void>;
147
+ } & {
148
+ [Suffix in _Suffixes as `loadMoreOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (options?: FetchPolicy) => Promise<void>;
147
149
  } & {
148
150
  [Suffix in _Suffixes as `setQueryArgsOf${_CapRef}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: (...args: [...args: StoreSliceArgs<SlceCls, Suffix>, options?: FetchPolicy] | [
149
151
  setQueryArgs: (...prevQueryArgs: StoreSliceArgs<SlceCls, Suffix>) => StoreSliceArgs<SlceCls, Suffix>,
@@ -112,7 +112,7 @@ declare const RootStore_base: import("./rootStore.d.ts").RootStoreCls<"root", im
112
112
  } & {
113
113
  [x: `setPageOf${any}`]: (page: number, options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
114
114
  } & {
115
- [x: `addPageOf${any}`]: (page: number, options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
115
+ [x: `loadMoreOf${any}`]: (options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
116
116
  } & {
117
117
  [x: `setLimitOf${any}`]: (limit: number, options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
118
118
  } & {
@@ -418,7 +418,7 @@ export declare const st: import("./agentic.d.ts").StAgentic & {
418
418
  } & {
419
419
  [x: `setPageOf${any}`]: (page: number, options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
420
420
  } & {
421
- [x: `addPageOf${any}`]: (page: number, options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
421
+ [x: `loadMoreOf${any}`]: (options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
422
422
  } & {
423
423
  [x: `setLimitOf${any}`]: (limit: number, options?: import("../common.d.ts").FetchPolicy) => Promise<void>;
424
424
  } & {
@@ -9,6 +9,10 @@ export interface LivePlacementProps {
9
9
  /** Which page of the list is on screen. Only the first can place a row. */
10
10
  page: number;
11
11
  limit: number;
12
+ /** Whether the list is pages `1..N` concatenated rather than one window. */
13
+ cumulative: boolean;
14
+ /** Whether the server still holds rows past the ones in hand. */
15
+ hasMore: boolean;
12
16
  /** The sort key the window is ordered by. */
13
17
  sortKey: string;
14
18
  /** The sort keys the slice declared a subscriber may reproduce. */
@@ -37,4 +41,4 @@ export interface LivePlacementProps {
37
41
  * And every sorted field has to be on the row, because a comparison against a value that is not there silently
38
42
  * places the row at one end.
39
43
  */
40
- export declare const livePlacementIndex: ({ list, row, page, limit, sortKey, allowedSorts, sorts, }: LivePlacementProps) => number | null;
44
+ export declare const livePlacementIndex: ({ list, row, page, limit, cumulative, hasMore, sortKey, allowedSorts, sorts, }: LivePlacementProps) => number | null;
@@ -1,7 +1,7 @@
1
1
  import type { SerializedArg } from "akanjs/signal";
2
2
  import type { SliceStateKey } from "./state.d.ts";
3
3
  /** The generated slice actions, named by what they do rather than by the key any one slice publishes them under. */
4
- export type SliceActionKey = "initModel" | "refreshModel" | "selectModel" | "setPageOfModel" | "addPageOfModel" | "setLimitOfModel" | "setQueryArgsOfModel" | "setSortOfModel" | "applyLiveModel" | "watchLiveModel";
4
+ export type SliceActionKey = "initModel" | "refreshModel" | "selectModel" | "setPageOfModel" | "loadMoreOfModel" | "setLimitOfModel" | "setQueryArgsOfModel" | "setSortOfModel" | "applyLiveModel" | "watchLiveModel";
5
5
  /**
6
6
  * What a generated key on `st.do` / `st.use` actually is.
7
7
  *
@@ -25,7 +25,7 @@ export interface DraftState {
25
25
  /** Set while a draft is the thing in the form. Drives the "continuing, saved n minutes ago" chip. */
26
26
  appliedAt: Date | null;
27
27
  }
28
- export type SliceStateKey = "defaultModel" | "modelInsight" | "modelList" | "modelListLoading" | "modelInitList" | "modelInitAt" | "modelStaleAt" | "modelSelection" | "lastPageOfModel" | "pageOfModel" | "limitOfModel" | "queryArgsOfModel" | "sortOfModel";
28
+ export type SliceStateKey = "defaultModel" | "modelInsight" | "modelList" | "modelListLoading" | "modelInitList" | "modelInitAt" | "modelStaleAt" | "modelSelection" | "lastPageOfModel" | "pageOfModel" | "limitOfModel" | "hasMoreOfModel" | "isCumulativeOfModel" | "queryArgsOfModel" | "sortOfModel";
29
29
  type _SliceMap<S extends SliceCls> = StoreSliceMap<S>;
30
30
  type _StateRefName<S extends SliceCls> = SlceCnstRefName<S>;
31
31
  type _StateCap<S extends SliceCls> = SlceCnstCapitalizedRefName<S>;
@@ -78,6 +78,8 @@ export type SliceState<RefName extends string, Suffix extends string, Full, Ligh
78
78
  [K in `pageOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: number;
79
79
  } & {
80
80
  [K in `limitOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: number;
81
+ } & {
82
+ [K in `hasMoreOf${_CapitalizedRefName}${_CapitalizedSuffix}` | `isCumulativeOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: boolean;
81
83
  } & {
82
84
  [K in `queryArgsOf${_CapitalizedRefName}${_CapitalizedSuffix}`]: Args;
83
85
  } & {
@@ -97,6 +99,8 @@ type DefaultSliceStateFields<SlceCls extends SliceCls, _RefName extends string,
97
99
  [Suffix in _Suffixes as `${_RefName}Insight${StoreSliceSuffixCap<SlceCls, Suffix>}`]: _Insight;
98
100
  } & {
99
101
  [Suffix in _Suffixes as `lastPageOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}` | `pageOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}` | `limitOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: number;
102
+ } & {
103
+ [Suffix in _Suffixes as `hasMoreOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}` | `isCumulativeOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: boolean;
100
104
  } & {
101
105
  [Suffix in _Suffixes as `queryArgsOf${_CapRefName}${StoreSliceSuffixCap<SlceCls, Suffix>}`]: StoreSliceArgs<SlceCls, Suffix>;
102
106
  } & {
@@ -1,12 +1,7 @@
1
1
  export interface InfiniteScrollProps {
2
- total: number;
3
- currentPage: number;
4
- itemsPerPage: number;
5
- onAddPage: (page: number) => Promise<void>;
6
- onPageSelect: (page: number, option?: {
7
- scrollToTop?: boolean;
8
- }) => void;
2
+ hasMore: boolean;
3
+ onLoadMore: () => Promise<void>;
9
4
  children: React.ReactNode;
10
5
  reverse?: boolean;
11
6
  }
12
- export declare const InfiniteScroll: ({ itemsPerPage, currentPage, total, onPageSelect, onAddPage, children, reverse, }: InfiniteScrollProps) => import("react/jsx-runtime").JSX.Element;
7
+ export declare const InfiniteScroll: ({ hasMore, onLoadMore, children, reverse }: InfiniteScrollProps) => import("react/jsx-runtime").JSX.Element;
@@ -2,7 +2,8 @@ interface MoreProps {
2
2
  total: number;
3
3
  itemsPerPage: number;
4
4
  currentPage: number;
5
- onAddPage: (page: number) => Promise<void>;
5
+ hasMore: boolean;
6
+ onLoadMore: () => Promise<void>;
6
7
  onPageSelect: (page: number, option?: {
7
8
  scrollToTop?: boolean;
8
9
  }) => void;
@@ -10,5 +11,5 @@ interface MoreProps {
10
11
  className?: string;
11
12
  reverse?: boolean;
12
13
  }
13
- export declare const More: ({ total, itemsPerPage, currentPage, onAddPage, onPageSelect, children, className, reverse, }: MoreProps) => import("react/jsx-runtime").JSX.Element;
14
+ export declare const More: ({ total, itemsPerPage, currentPage, hasMore, onLoadMore, onPageSelect, children, className, reverse, }: MoreProps) => import("react/jsx-runtime").JSX.Element;
14
15
  export {};
@@ -3,29 +3,16 @@ import { useEffect, useRef, useState } from "react";
3
3
  import { BiLoaderAlt } from "react-icons/bi";
4
4
 
5
5
  export interface InfiniteScrollProps {
6
- total: number;
7
- currentPage: number;
8
- itemsPerPage: number;
9
- onAddPage: (page: number) => Promise<void>;
10
- onPageSelect: (page: number, option?: { scrollToTop?: boolean }) => void;
6
+ hasMore: boolean;
7
+ onLoadMore: () => Promise<void>;
11
8
  children: React.ReactNode;
12
9
  reverse?: boolean;
13
10
  }
14
11
 
15
- export const InfiniteScroll = ({
16
- itemsPerPage,
17
- currentPage,
18
- total,
19
- onPageSelect,
20
- onAddPage,
21
- children,
22
- reverse,
23
- }: InfiniteScrollProps) => {
12
+ export const InfiniteScroll = ({ hasMore, onLoadMore, children, reverse }: InfiniteScrollProps) => {
24
13
  const [isFetching, setIsFetching] = useState(false);
25
14
  const isFetchingRef = useRef(false);
26
15
  const target = useRef<HTMLDivElement>(null);
27
- const page = useRef<number>(currentPage);
28
- const totalPages = Math.ceil(total / (itemsPerPage || 1));
29
16
 
30
17
  useEffect(() => {
31
18
  const observer = new IntersectionObserver((entries) => {
@@ -36,12 +23,10 @@ export const InfiniteScroll = ({
36
23
  return () => {
37
24
  observer.disconnect();
38
25
  };
39
- }, []);
26
+ }, [hasMore]);
40
27
 
41
28
  const fetchMoreItems = async () => {
42
29
  if (isFetchingRef.current) return;
43
- const nextPage = page.current + 1;
44
- if (nextPage > totalPages) return;
45
30
 
46
31
  const scroller = reverse ? document.scrollingElement : null;
47
32
  const prevScrollHeight = scroller?.scrollHeight ?? 0;
@@ -50,9 +35,7 @@ export const InfiniteScroll = ({
50
35
  isFetchingRef.current = true;
51
36
  setIsFetching(true);
52
37
  try {
53
- await onAddPage(nextPage);
54
- onPageSelect(nextPage, { scrollToTop: false });
55
- page.current = nextPage;
38
+ await onLoadMore();
56
39
 
57
40
  const restoreScroll = () => {
58
41
  if (scroller) {
@@ -76,15 +59,15 @@ export const InfiniteScroll = ({
76
59
 
77
60
  return (
78
61
  <>
79
- {reverse ? (
62
+ {reverse && hasMore ? (
80
63
  <div ref={target} className="flex w-full items-end justify-center">
81
- {isFetching && <BiLoaderAlt className="h-10 animate-spin pb-4 text-2xl" />}
64
+ {isFetching ? <BiLoaderAlt className="h-10 animate-spin pb-4 text-2xl" /> : null}
82
65
  </div>
83
66
  ) : null}
84
67
  {children}
85
- {!reverse ? (
68
+ {!reverse && hasMore ? (
86
69
  <div ref={target} className="flex h-32 w-full justify-center pt-4">
87
- {isFetching && <BiLoaderAlt className="animate-spin text-2xl" />}
70
+ {isFetching ? <BiLoaderAlt className="animate-spin text-2xl" /> : null}
88
71
  </div>
89
72
  ) : null}
90
73
  </>
@@ -39,7 +39,6 @@ function Render<RefName extends string, Light>({ className, init, scrollToTop }:
39
39
  lastPageOfModel: `lastPageOf${ModelName}`,
40
40
  limitOfModel: `limitOf${ModelName}`,
41
41
  setPageOfModel: `setPageOf${ModelName}`,
42
- addPageOfModel: `addPageOf${ModelName}`,
43
42
  };
44
43
  const namesOfSlice = {
45
44
  modelInsight: sliceName.replace(names.model, names.modelInsight),
@@ -47,7 +46,6 @@ function Render<RefName extends string, Light>({ className, init, scrollToTop }:
47
46
  lastPageOfModel: sliceName.replace(names.model, names.lastPageOfModel),
48
47
  pageOfModel: sliceName.replace(names.model, names.pageOfModel),
49
48
  setPageOfModel: sliceName.replace(names.model, names.setPageOfModel),
50
- addPageOfModel: sliceName.replace(names.model, names.addPageOfModel),
51
49
  };
52
50
  const modelInsight = storeUse[namesOfSlice.modelInsight]() as BaseInsight;
53
51
  const limitOfModel = storeUse[namesOfSlice.limitOfModel]() as number;
package/ui/Load/Units.tsx CHANGED
@@ -85,10 +85,12 @@ function Render<RefName extends string, Light extends { id: string }>({
85
85
  pageOfModel: `pageOf${ModelName}`,
86
86
  lastPageOfModel: `lastPageOf${ModelName}`,
87
87
  limitOfModel: `limitOf${ModelName}`,
88
+ hasMoreOfModel: `hasMoreOf${ModelName}`,
89
+ isCumulativeOfModel: `isCumulativeOf${ModelName}`,
88
90
  queryArgsOfModel: `queryArgsOf${ModelName}`,
89
91
  sortOfModel: `sortOf${ModelName}`,
90
92
  setPageOfModel: `setPageOf${ModelName}`,
91
- addPageOfModel: `addPageOf${ModelName}`,
93
+ loadMoreOfModel: `loadMoreOf${ModelName}`,
92
94
  refreshModel: `refresh${ModelName}`,
93
95
  watchLiveModel: `watchLive${ModelName}`,
94
96
  };
@@ -102,10 +104,12 @@ function Render<RefName extends string, Light extends { id: string }>({
102
104
  pageOfModel: sliceName.replace(names.model, names.pageOfModel),
103
105
  lastPageOfModel: sliceName.replace(names.model, names.lastPageOfModel),
104
106
  limitOfModel: sliceName.replace(names.model, names.limitOfModel),
107
+ hasMoreOfModel: sliceName.replace(names.model, names.hasMoreOfModel),
108
+ isCumulativeOfModel: sliceName.replace(names.model, names.isCumulativeOfModel),
105
109
  queryArgsOfModel: sliceName.replace(names.model, names.queryArgsOfModel),
106
110
  sortOfModel: sliceName.replace(names.model, names.sortOfModel),
107
111
  setPageOfModel: sliceName.replace(names.model, names.setPageOfModel),
108
- addPageOfModel: sliceName.replace(names.model, names.addPageOfModel),
112
+ loadMoreOfModel: sliceName.replace(names.model, names.loadMoreOfModel),
109
113
  refreshModel: sliceName.replace(names.model, names.refreshModel),
110
114
  watchLiveModel: sliceName.replace(names.model, names.watchLiveModel),
111
115
  };
@@ -116,6 +120,7 @@ function Render<RefName extends string, Light extends { id: string }>({
116
120
  const initModelObjInsight = (init as DynamicRecord)[names.modelObjInsight] as BaseInsight | null;
117
121
  const initLimitOfModel = (init as DynamicRecord)[names.limitOfModel] as number;
118
122
  const initPageOfModel = (init as DynamicRecord)[names.pageOfModel] as number;
123
+ const initHasMoreOfModel = (init as DynamicRecord)[names.hasMoreOfModel] as boolean;
119
124
  const initSignature = JSON.stringify(initQueryArgs);
120
125
 
121
126
  const useCache =
@@ -155,6 +160,9 @@ function Render<RefName extends string, Light extends { id: string }>({
155
160
  [namesOfSlice.pageOfModel]: initPageOfModel,
156
161
  [namesOfSlice.lastPageOfModel]: initLastPageOfModel,
157
162
  [namesOfSlice.limitOfModel]: initLimitOfModel,
163
+
164
+ [namesOfSlice.hasMoreOfModel]: initHasMoreOfModel,
165
+ [namesOfSlice.isCumulativeOfModel]: false,
158
166
  [namesOfSlice.queryArgsOfModel]: initQueryArgsOfModel,
159
167
  [namesOfSlice.sortOfModel]: initSortOfModel,
160
168
  });
@@ -180,6 +188,7 @@ function Render<RefName extends string, Light extends { id: string }>({
180
188
  const modelInsight = storeUse[namesOfSlice.modelInsight]() as BaseInsight;
181
189
  const limitOfModel = storeUse[namesOfSlice.limitOfModel]() as number;
182
190
  const pageOfModel = storeUse[namesOfSlice.pageOfModel]() as number;
191
+ const hasMoreOfModel = storeUse[namesOfSlice.hasMoreOfModel]() as boolean;
183
192
  const insight = loaded ? modelInsight : initModelObjInsight;
184
193
  const limit = loaded ? limitOfModel : initLimitOfModel;
185
194
  const page = loaded ? pageOfModel : initPageOfModel;
@@ -188,8 +197,9 @@ function Render<RefName extends string, Light extends { id: string }>({
188
197
  total,
189
198
  currentPage: page,
190
199
  itemsPerPage: limit || total,
191
- onAddPage: async (page: number) => {
192
- await storeDo[namesOfSlice.addPageOfModel](page);
200
+ hasMore: loaded ? hasMoreOfModel : initHasMoreOfModel,
201
+ onLoadMore: async () => {
202
+ await storeDo[namesOfSlice.loadMoreOfModel]();
193
203
  },
194
204
  onPageSelect: (page: number, option?: { scrollToTop?: boolean }) => {
195
205
  void storeDo[namesOfSlice.setPageOfModel](page);
@@ -342,7 +352,8 @@ interface MoreProps {
342
352
  total: number;
343
353
  itemsPerPage: number;
344
354
  currentPage: number;
345
- onAddPage: (page: number) => Promise<void>;
355
+ hasMore: boolean;
356
+ onLoadMore: () => Promise<void>;
346
357
  onPageSelect: (page: number, option?: { scrollToTop?: boolean }) => void;
347
358
  children?: React.ReactNode;
348
359
  className?: string;
package/ui/More.tsx CHANGED
@@ -8,7 +8,8 @@ interface MoreProps {
8
8
  total: number;
9
9
  itemsPerPage: number;
10
10
  currentPage: number;
11
- onAddPage: (page: number) => Promise<void>;
11
+ hasMore: boolean;
12
+ onLoadMore: () => Promise<void>;
12
13
  onPageSelect: (page: number, option?: { scrollToTop?: boolean }) => void;
13
14
  children?: React.ReactNode;
14
15
  className?: string;
@@ -19,7 +20,8 @@ export const More = ({
19
20
  total,
20
21
  itemsPerPage,
21
22
  currentPage,
22
- onAddPage,
23
+ hasMore,
24
+ onLoadMore,
23
25
  onPageSelect,
24
26
  children,
25
27
  className,
@@ -31,24 +33,15 @@ export const More = ({
31
33
  setIsMobile(isMobileDevice());
32
34
  }, []);
33
35
 
34
- if (total <= itemsPerPage) {
35
- return <>{children}</>;
36
- }
37
-
38
- if (isMobile) {
36
+ if (isMobile)
39
37
  return (
40
- <InfiniteScroll
41
- total={total}
42
- currentPage={currentPage}
43
- itemsPerPage={itemsPerPage}
44
- onAddPage={onAddPage}
45
- onPageSelect={onPageSelect}
46
- reverse={reverse}
47
- >
38
+ <InfiniteScroll hasMore={hasMore} onLoadMore={onLoadMore} reverse={reverse}>
48
39
  {children}
49
40
  </InfiniteScroll>
50
41
  );
51
- }
42
+
43
+ if (total <= itemsPerPage) return <>{children}</>;
44
+
52
45
  return (
53
46
  <>
54
47
  {children}