@marktiderman/genesis-ui 2.10.1 → 2.11.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/SKILL.md CHANGED
@@ -55,13 +55,14 @@ Reach for these before a slot. Each one replaced a slot workaround in a real app
55
55
 
56
56
  | You need | Prop |
57
57
  | --- | --- |
58
- | Every row reachable in `resource` mode | default `pagination="client"` fetches up to `clientRowLimit` (1000) and says so past it; `pagination="server"` pages through the provider (give it `searchFields`) |
58
+ | Every row reachable in `resource` mode | default `pagination="client"` fetches up to `clientRowLimit` (1000) and says so past it; `pagination="server"` pages through the provider (give it `searchFields`); `pagination="cursor"` loads `resourceOptions.defaultPerPage` rows with a "Load more" button (keyset where the provider serves it; toolbar search and sort act on loaded rows) |
59
59
  | Who may create, edit, delete | `can(action, item)` (UI gate only; the server still decides) |
60
60
  | A confirmed delete | `allowDelete` (built in, `resource` mode), or `onDelete` + `confirmDelete`; copy via `confirmDelete={{ title: (item) => … }}`, toast via `onDeleted` |
61
61
  | A computed peek subtitle | `getSubtitle(item)`; the peek stays open after an edit and shows the saved row |
62
62
  | A list inside a tab (no page title) | `header="toolbar"` (also on `DataPageShell`) |
63
63
  | Empty-state copy | `emptyMessage`, `emptyDescription`, `emptyIcon` |
64
64
  | Cards on phones, table on desktop | `defaultView={{ mobile: "grid", desktop: "table" }}`; only an explicit toggle is stored |
65
+ | A list that updates when others change it | `live` (needs a provider whose `subscribe` serves the resource; Supabase: `resources[r].realtime` plus the table in the `supabase_realtime` publication, see core's README) |
65
66
  | Typed slot maps | `ResourcePageSlots<T>`, `ResourcePageSlotProps<T>`, `ResourcePageSlotMap<T>` from `/data` |
66
67
 
67
68
  `ResourceForm` ties labels to inputs (`getByLabelText` works), shows a failed save as an alert,
@@ -6,7 +6,7 @@ import { toast } from './chunk-4RAZJ6LL.js';
6
6
  import { defaultNavigate } from './chunk-WGMEVX3Y.js';
7
7
  import { useKeyboardNavigation } from './chunk-YJ6MPS6U.js';
8
8
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
9
- import { useResource, useOne, useCapabilities } from '@marktiderman/genesis-core/hooks';
9
+ import { useResource, useInfiniteResource, useOne, useCapabilities } from '@marktiderman/genesis-core/hooks';
10
10
 
11
11
  // src/components/data/undo-toast.ts
12
12
  var DONE = {
@@ -122,6 +122,7 @@ function useResourceTableModel(props) {
122
122
  emptyDescription,
123
123
  header: headerMode = "page",
124
124
  resourceOptions,
125
+ live,
125
126
  urlState: urlStateProp,
126
127
  defaultView = "table",
127
128
  onNavigate = defaultNavigate,
@@ -132,6 +133,7 @@ function useResourceTableModel(props) {
132
133
  } = props;
133
134
  const isClientMode = !!resourceName;
134
135
  const resourceServerPaging = isClientMode && pagination === "server";
136
+ const resourceCursorPaging = isClientMode && pagination === "cursor";
135
137
  const url = useResourceUrlState(
136
138
  urlStateProp ? "setSearchParams" in urlStateProp ? { searchParams: urlStateProp } : urlStateProp : void 0
137
139
  );
@@ -167,14 +169,26 @@ function useResourceTableModel(props) {
167
169
  ...urlSeed.page ? { defaultPage: urlSeed.page } : {},
168
170
  ...urlSeed.sort ? { defaultSort: urlSeed.sort } : {}
169
171
  } : {},
170
- ...writeModeOptions(resourceOptions, mutationModeProp)
172
+ ...writeModeOptions(resourceOptions, mutationModeProp),
173
+ // Cursor paging reads through useInfiniteResource below; this hook
174
+ // only writes.
175
+ ...resourceCursorPaging ? { enabled: false } : {},
176
+ live: resourceOptions?.live ?? live
171
177
  } : { enabled: false }
172
178
  );
179
+ const infinite = useInfiniteResource(resourceName ?? "__noop__", {
180
+ enabled: resourceCursorPaging && (resourceOptions?.enabled ?? true),
181
+ perPage: resourceOptions?.defaultPerPage ?? perPageProp ?? settings.pageSize,
182
+ defaultSort: resourceOptions?.defaultSort,
183
+ defaultFilters: resourceOptions?.defaultFilters,
184
+ select: resourceOptions?.select,
185
+ live: resourceOptions?.live ?? live
186
+ });
173
187
  const effectivePageSize = resourceServerPaging ? resourceHook.params.pagination?.perPage ?? settings.pageSize : perPageProp ?? settings.pageSize;
174
- const displayData = isClientMode ? resourceHook.list.data?.data ?? [] : dataProp ?? [];
175
- const displayTotal = isClientMode ? resourceHook.list.data?.total ?? 0 : totalProp ?? 0;
176
- const isLoading = isClientMode ? resourceHook.list.isLoading : loadingProp ?? false;
177
- const hasError = isClientMode ? resourceHook.list.isError : errorProp ?? false;
188
+ const displayData = resourceCursorPaging ? infinite.rows : isClientMode ? resourceHook.list.data?.data ?? [] : dataProp ?? [];
189
+ const displayTotal = resourceCursorPaging ? infinite.total ?? infinite.rows.length : isClientMode ? resourceHook.list.data?.total ?? 0 : totalProp ?? 0;
190
+ const isLoading = resourceCursorPaging ? infinite.query.isLoading : isClientMode ? resourceHook.list.isLoading : loadingProp ?? false;
191
+ const hasError = resourceCursorPaging ? infinite.query.isError : isClientMode ? resourceHook.list.isError : errorProp ?? false;
178
192
  const page = useResourcePage({
179
193
  resource: resourceName,
180
194
  data: displayData,
@@ -247,7 +261,7 @@ function useResourceTableModel(props) {
247
261
  defaultFilters,
248
262
  setResourceFilters
249
263
  ]);
250
- const clientTruncated = isClientMode && !resourceServerPaging && !isLoading && !hasError && displayTotal > displayData.length;
264
+ const clientTruncated = isClientMode && !resourceServerPaging && !resourceCursorPaging && !isLoading && !hasError && displayTotal > displayData.length;
251
265
  const warnedTruncatedRef = useRef(false);
252
266
  useEffect(() => {
253
267
  if (!clientTruncated || warnedTruncatedRef.current) return;
@@ -256,12 +270,14 @@ function useResourceTableModel(props) {
256
270
  `ResourcePage: "${resourceName}" has ${displayTotal} rows but only ${displayData.length} were fetched. Pass \`pagination="server"\` to page through all of them, or raise \`clientRowLimit\` (no higher than the provider's \`maxPerPage\`, which refuses a larger page).`
257
271
  );
258
272
  }, [clientTruncated, resourceName, displayTotal, displayData.length]);
273
+ const refetchList = resourceHook.list.refetch;
274
+ const refetchInfinite = infinite.query.refetch;
259
275
  const actions = useMemo(() => {
260
276
  if (isClientMode) {
261
277
  return {
262
278
  update: resourceHook.update,
263
279
  remove: resourceHook.remove,
264
- refetch: () => resourceHook.list.refetch()
280
+ refetch: () => resourceCursorPaging ? refetchInfinite() : refetchList()
265
281
  };
266
282
  }
267
283
  return {
@@ -282,7 +298,9 @@ function useResourceTableModel(props) {
282
298
  isClientMode,
283
299
  resourceHook.update,
284
300
  resourceHook.remove,
285
- resourceHook.list
301
+ refetchList,
302
+ resourceCursorPaging,
303
+ refetchInfinite
286
304
  ]);
287
305
  const keyOf = useCallback(
288
306
  (item) => getKeyProp ? getKeyProp(item) : String(item.id ?? ""),
@@ -629,10 +647,20 @@ function useResourceTableModel(props) {
629
647
  // and modal modes, so it is the precise precondition here.
630
648
  onBack: detailOpen ? closeDetail : void 0
631
649
  });
650
+ const infinitePages = infinite.query.data?.pages;
651
+ const loadMore = resourceCursorPaging ? {
652
+ hasMore: infinite.hasNextPage,
653
+ loading: infinite.isFetchingNextPage,
654
+ load: infinite.fetchNextPage,
655
+ loaded: infinite.rows.length,
656
+ // Rows the latest "Load more" added; 0 before the first one.
657
+ lastLoaded: infinitePages && infinitePages.length > 1 ? infinitePages[infinitePages.length - 1].data.length : 0
658
+ } : void 0;
632
659
  return {
633
660
  actions,
634
661
  allows,
635
662
  clientTruncated,
663
+ loadMore,
636
664
  closeDetail,
637
665
  currentPage,
638
666
  deleteConfirmed,
@@ -706,10 +734,12 @@ function useResourceTable(props) {
706
734
  );
707
735
  return {
708
736
  rows: m.page.data,
709
- total: m.page.total,
737
+ total: m.loadMore && m.page.search === "" && m.page.statusValue.length === 0 ? m.displayTotal : m.page.total,
710
738
  loading: m.isLoading,
711
739
  error: m.hasError,
712
740
  truncated: m.clientTruncated,
741
+ // Present only with `pagination="cursor"`.
742
+ ...m.loadMore ? { loadMore: m.loadMore } : {},
713
743
  columns: m.tableColumns,
714
744
  actions: m.actions,
715
745
  getKey: m.keyOf,
@@ -759,5 +789,5 @@ function useResourceTable(props) {
759
789
  }
760
790
 
761
791
  export { showUndoToast, showWriteErrorToast, useResourceTable, useResourceTableModel };
762
- //# sourceMappingURL=chunk-C7PQ54JI.js.map
763
- //# sourceMappingURL=chunk-C7PQ54JI.js.map
792
+ //# sourceMappingURL=chunk-5GOQ72WB.js.map
793
+ //# sourceMappingURL=chunk-5GOQ72WB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data/undo-toast.ts","../src/components/data/use-resource-table.ts"],"names":[],"mappings":"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","file":"chunk-5GOQ72WB.js","sourcesContent":["import type { MutationMode, UndoableWrite } from \"@marktiderman/genesis-core/hooks\";\nimport { toast } from \"../ui/sonner\";\n\nconst DONE: Record<UndoableWrite[\"kind\"], string> = {\n create: \"Created.\",\n update: \"Saved.\",\n delete: \"Deleted.\",\n};\n\n/**\n * The undo toast for an `undoable` write (core's `onUndoable`): a sonner\n * toast with an Undo button, shown for as long as the undo is offered.\n * Needs a mounted `<Toaster />`.\n */\nexport function showUndoToast(write: UndoableWrite): void {\n toast(DONE[write.kind], {\n duration: write.delayMs,\n action: {\n label: \"Undo\",\n onClick: () => {\n void write.undo().then((undone) => {\n // Sonner pauses on hover, so the button can outlive the window.\n if (!undone && write.strategy === \"delay\") {\n toast(\"Already saved. It can no longer be undone.\");\n }\n });\n },\n },\n });\n}\n\n/** The default error report for writes that fail after the form has closed. */\nexport function showWriteErrorToast(error: Error): void {\n toast.error(error.message || \"The change could not be saved.\");\n}\n\n/**\n * ResourcePage's write-mode options for its `useResource`: the page's\n * `mutationMode` unless `resourceOptions` sets one, the undo toast, and an\n * error toast for a write that fails after it was shown as done.\n */\nexport function writeModeOptions(\n resourceOptions: { mutationMode?: MutationMode; onUndoable?: (write: UndoableWrite) => void; onError?: (error: Error) => void } | undefined,\n mutationMode: MutationMode | undefined,\n): { mutationMode: MutationMode; onUndoable: (write: UndoableWrite) => void; onError?: (error: Error) => void } {\n const mode = resourceOptions?.mutationMode ?? mutationMode ?? \"pessimistic\";\n return {\n mutationMode: mode,\n onUndoable: resourceOptions?.onUndoable ?? showUndoToast,\n onError: resourceOptions?.onError ?? (mode === \"pessimistic\" ? undefined : showWriteErrorToast),\n };\n}\n","\"use client\";\n\n/**\n * `useResourceTable`: ResourcePage's behaviour without its markup. Lives\n * apart from ResourcePage.tsx so importing the hook (e.g. from `/hooks`)\n * does not pull in the page's components and icons.\n */\nimport { useMemo, useState, useCallback, useEffect, useRef } from \"react\";\nimport type { DataTableColumn, DataTableSort } from \"./DataTable\";\nimport type {\n ResourceAction,\n ResourceDeleteConfirmCopy,\n ResourcePageProps,\n} from \"./ResourcePage\";\nimport { useViewSettings } from \"../../hooks/use-view-settings\";\nimport { useKeyboardNavigation } from \"../../hooks/use-keyboard-navigation\";\nimport { useResourceUrlState } from \"../../hooks/use-resource-url-state\";\nimport {\n useResourcePage,\n type ResourceActions,\n} from \"../../hooks/use-resource-page\";\nimport {\n useCapabilities,\n useInfiniteResource,\n useOne,\n useResource,\n} from \"@marktiderman/genesis-core/hooks\";\nimport type {\n BaseRecord,\n Identifier,\n} from \"@marktiderman/genesis-core/provider\";\nimport { writeModeOptions } from \"./undo-toast\";\nimport { toast } from \"../ui/sonner\";\nimport { defaultNavigate } from \"../../navigation\";\n\n/**\n * Everything ResourcePage's markup reads. Internal: its names may change in\n * any release. Apps use {@link useResourceTable}.\n *\n * @internal\n */\nexport function useResourceTableModel<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(props: ResourcePageProps<T>) {\n const {\n resource: resourceName,\n data: dataProp,\n total: totalProp,\n pagination = \"client\",\n clientRowLimit = 1000,\n title,\n subtitle,\n icon,\n columns: columnsProp,\n getKey: getKeyProp,\n detail = \"panel\",\n detailWidth = \"md\",\n titleField: titleFieldProp,\n subtitleField: subtitleFieldProp,\n getTitle,\n getSubtitle,\n renderDetail: renderDetailProp,\n detailFields,\n statusFilter,\n searchFields,\n searchPlaceholder,\n initialFilters,\n onFiltersChange,\n filters: filtersProp,\n page: pageProp,\n perPage: perPageProp,\n onPageChange,\n onPerPageChange,\n onSortChange,\n onSearchChange,\n loading: loadingProp,\n error: errorProp,\n errorMessage,\n onRetry: onRetryProp,\n savedViews,\n onLoadView,\n onSaveView,\n onDeleteView,\n canSaveViews,\n columnVisibility,\n columnVisibilityKey,\n resizableColumns,\n resizeKey,\n stickyHeader,\n stickyFirstColumn,\n pageSizeOptions,\n onCreate,\n onEdit,\n onDelete,\n can,\n canEditItem,\n createLabel = \"New\",\n canEdit,\n canDelete,\n allowDelete,\n confirmDelete,\n onDeleted,\n secondaryActions,\n allowCreate,\n allowEdit,\n formFields,\n renderForm: renderFormProp,\n formMode,\n formSteps,\n formLayout: formLayoutProp,\n mutationMode: mutationModeProp,\n bulkActions,\n renderCard: renderCardProp,\n openOnCardClick = false,\n renderTable: renderTableProp,\n gridCols: gridColsProp,\n keyboardNavigation = true,\n capabilities,\n toolbar,\n emptyState,\n emptyIcon,\n emptyMessage,\n emptyDescription,\n header: headerMode = \"page\",\n resourceOptions,\n live,\n urlState: urlStateProp,\n defaultView = \"table\",\n onNavigate = defaultNavigate,\n viewSettingsKey: viewSettingsKeyProp,\n slots,\n slotProps,\n className,\n } = props;\n\n // ── Determine mode ──\n const isClientMode = !!resourceName;\n\n /**\n * Server-controlled mode is entered by supplying ANY of the controlled\n * props — presence is the switch, so a caller that only pages (no server\n * search) still gets its `data` left alone.\n */\n // `resource` mode paging through the provider: ResourcePage owns both the\n // fetch and the page state, and reuses the server-controlled path below\n // (no client-side transform, the pager under the list) with useResource's\n // setters standing in for the caller's callbacks.\n const resourceServerPaging = isClientMode && pagination === \"server\";\n // `pagination=\"cursor\"`: pages load by cursor and append (\"Load more\").\n const resourceCursorPaging = isClientMode && pagination === \"cursor\";\n\n // ── URL state (`urlState`): the open record, search, sort and page ──\n // An adapter has `setSearchParams`; the options object wraps one.\n const url = useResourceUrlState(\n urlStateProp\n ? \"setSearchParams\" in urlStateProp\n ? { searchParams: urlStateProp }\n : urlStateProp\n : undefined,\n );\n // Read once: the URL seeds the list when it mounts, then follows it.\n const [urlSeed] = useState(() => {\n if (!url) return null;\n // A URL sort only counts when it names a sortable column: a hand-edited\n // or stale link must not send an arbitrary field to the provider.\n const sortable =\n !url.sort || !columnsProp\n ? true\n : columnsProp.some((col) =>\n typeof col === \"string\"\n ? col === url.sort!.field\n : col.key === url.sort!.field && col.sortable !== false,\n );\n return { q: url.q, sort: sortable ? url.sort : null, page: url.page };\n });\n // The URL names the open record only where there is a peek to open.\n const urlPeek = detail === \"panel\" || detail === \"modal\" ? url : undefined;\n const urlSortKey = urlSeed?.sort\n ? `${urlSeed.sort.field}:${urlSeed.sort.order}`\n : \"\";\n\n const isServerControlled =\n resourceServerPaging ||\n pageProp !== undefined ||\n perPageProp !== undefined ||\n onPageChange !== undefined ||\n onPerPageChange !== undefined ||\n onSortChange !== undefined ||\n onSearchChange !== undefined;\n\n const viewSettingsKey =\n viewSettingsKeyProp ??\n resourceName ??\n title.toLowerCase().replace(/\\s+/g, \"-\");\n const { settings, updateSetting } = useViewSettings(viewSettingsKey);\n\n // Server search needs the columns to match. Without them the provider\n // would get a query it ignores, so the search box hides instead of lying.\n const serverSearchFields = resourceOptions?.searchFields ?? searchFields;\n\n // ── Client-side mode: call useResource (hook is always called for rules-of-hooks) ──\n // Core's useResource constrains its row type to BaseRecord (rows carry an\n // `id`), which is stricter than ResourcePage's public `T extends\n // Record<string, unknown>`. Intersect at the call site so the public\n // ResourcePageProps<T> generic stays unchanged while satisfying the hook.\n const resourceHook = useResource<T & BaseRecord>(\n resourceName ?? \"__noop__\",\n isClientMode\n ? {\n ...resourceOptions,\n // Client paging fetches everything it can in one request; server\n // paging starts at the user's stored page size. An explicit\n // `defaultPerPage` wins over both.\n defaultPerPage:\n resourceOptions?.defaultPerPage ??\n (resourceServerPaging\n ? (perPageProp ?? settings.pageSize)\n : clientRowLimit),\n searchFields: resourceServerPaging\n ? serverSearchFields\n : resourceOptions?.searchFields,\n // Paging, searching and sorting change the query key; keep the\n // current page on screen while the next one loads.\n keepPreviousData:\n resourceOptions?.keepPreviousData ?? resourceServerPaging,\n // Start where the URL says, so restoring a link doesn't push a\n // search or sort that resets the page to 1.\n ...(resourceServerPaging && urlSeed\n ? {\n defaultSearch: urlSeed.q,\n ...(urlSeed.page ? { defaultPage: urlSeed.page } : {}),\n ...(urlSeed.sort ? { defaultSort: urlSeed.sort } : {}),\n }\n : {}),\n ...writeModeOptions(resourceOptions, mutationModeProp),\n // Cursor paging reads through useInfiniteResource below; this hook\n // only writes.\n ...(resourceCursorPaging ? { enabled: false } : {}),\n live: resourceOptions?.live ?? live,\n }\n : { enabled: false },\n );\n const infinite = useInfiniteResource<T & BaseRecord>(resourceName ?? \"__noop__\", {\n enabled: resourceCursorPaging && (resourceOptions?.enabled ?? true),\n perPage: resourceOptions?.defaultPerPage ?? perPageProp ?? settings.pageSize,\n defaultSort: resourceOptions?.defaultSort,\n defaultFilters: resourceOptions?.defaultFilters,\n select: resourceOptions?.select,\n live: resourceOptions?.live ?? live,\n });\n\n /** Explicit prop beats the per-user stored preference. */\n const effectivePageSize = resourceServerPaging\n ? (resourceHook.params.pagination?.perPage ?? settings.pageSize)\n : (perPageProp ?? settings.pageSize);\n\n // ── Resolve display data ──\n const displayData: T[] = resourceCursorPaging\n ? infinite.rows\n : isClientMode\n ? (resourceHook.list.data?.data ?? [])\n : (dataProp ?? []);\n const displayTotal: number = resourceCursorPaging\n ? (infinite.total ?? infinite.rows.length)\n : isClientMode\n ? (resourceHook.list.data?.total ?? 0)\n : (totalProp ?? 0);\n // Client-side mode reads the hook it owns; server-data mode reads the\n // caller's `loading` / `error` props, defaulting to false so a page that\n // supplies neither renders exactly as it did before they existed.\n const isLoading = resourceCursorPaging\n ? infinite.query.isLoading\n : isClientMode\n ? resourceHook.list.isLoading\n : (loadingProp ?? false);\n const hasError = resourceCursorPaging\n ? infinite.query.isError\n : isClientMode\n ? resourceHook.list.isError\n : (errorProp ?? false);\n\n // ── Call useResourcePage ──\n const page = useResourcePage<T>({\n resource: resourceName,\n data: displayData,\n total: displayTotal,\n columns: columnsProp,\n statusFilter,\n searchFields,\n initialFilters:\n initialFilters ??\n (urlSeed && (urlSeed.q || urlSeed.sort)\n ? [\n ...(urlSeed.q\n ? [{ field: \"__search__\", operator: \"contains\" as const, value: urlSeed.q }]\n : []),\n ...(urlSeed.sort\n ? [{ field: \"__sort__\", operator: \"eq\" as const, value: urlSortKey }]\n : []),\n ]\n : undefined),\n onFiltersChange,\n defaultView,\n // Each page remembers its own table/cards choice, under the same key as\n // its density and page size — not one key shared by every data page.\n viewKey: viewSettingsKey,\n detail,\n titleField: titleFieldProp,\n subtitleField: subtitleFieldProp,\n controlled: isServerControlled,\n });\n\n // ── `pagination=\"server\"`: push the filter row's state into useResource ──\n // Effects rather than the change handlers, so `initialFilters` and \"Clear\n // all\" reach the provider by the same path as a keystroke. Each push is\n // skipped while its value still equals what the hook already has, so the\n // first render does not reset `defaultSort` or refetch for nothing.\n const {\n setSearch: setResourceSearch,\n setSort: setResourceSort,\n setFilters: setResourceFilters,\n } = resourceHook;\n // Seeded from the URL, which useResource already starts on (see above).\n const pushedSearchRef = useRef(resourceServerPaging ? (urlSeed?.q ?? \"\") : \"\");\n const searchEffectRanRef = useRef(false);\n useEffect(() => {\n if (!resourceServerPaging) return;\n const firstRun = !searchEffectRanRef.current;\n searchEffectRanRef.current = true;\n if (page.search === pushedSearchRef.current) return;\n const push = () => {\n pushedSearchRef.current = page.search;\n setResourceSearch(page.search);\n };\n // One request per pause in typing, not per keystroke — but an\n // `initialFilters` search (first run) or a cleared box (\"Clear all\")\n // goes out at once, landing with the sort / status pushes instead of\n // 300ms behind them.\n if (firstRun || page.search === \"\") {\n push();\n return;\n }\n const timer = setTimeout(push, 300);\n return () => clearTimeout(timer);\n }, [resourceServerPaging, page.search, setResourceSearch]);\n\n const defaultSort = resourceOptions?.defaultSort;\n const pushedSortRef = useRef<string>(resourceServerPaging ? urlSortKey : \"\");\n useEffect(() => {\n if (!resourceServerPaging) return;\n const key = page.sort ? `${page.sort.field}:${page.sort.order}` : \"\";\n if (key === pushedSortRef.current) return;\n pushedSortRef.current = key;\n // Clearing the sort returns to the resource's default order, not to\n // whatever order the provider happens to use.\n setResourceSort(page.sort ?? defaultSort);\n }, [resourceServerPaging, page.sort, defaultSort, setResourceSort]);\n\n const statusField = statusFilter\n ? typeof statusFilter === \"string\"\n ? statusFilter\n : statusFilter.field\n : undefined;\n const defaultFilters = resourceOptions?.defaultFilters;\n const pushedStatusRef = useRef<string>(\"\");\n useEffect(() => {\n if (!resourceServerPaging || !statusField) return;\n // `defaultFilters` is part of the key: useResource re-syncs its filters\n // to a changed `defaultFilters` (dropping the status filter), so a\n // change there must re-push even when the chips did not move.\n const key = JSON.stringify([page.statusValue, defaultFilters ?? null]);\n if (key === pushedStatusRef.current) return;\n pushedStatusRef.current = key;\n setResourceFilters([\n ...(defaultFilters ?? []),\n ...(page.statusValue.length > 0\n ? [{ field: statusField, operator: \"in\" as const, value: page.statusValue }]\n : []),\n ]);\n }, [\n resourceServerPaging,\n statusField,\n page.statusValue,\n defaultFilters,\n setResourceFilters,\n ]);\n\n // ── `pagination=\"client\"`: say so when the resource outgrew the fetch ──\n const clientTruncated =\n isClientMode &&\n !resourceServerPaging &&\n !resourceCursorPaging &&\n !isLoading &&\n !hasError &&\n displayTotal > displayData.length;\n const warnedTruncatedRef = useRef(false);\n useEffect(() => {\n if (!clientTruncated || warnedTruncatedRef.current) return;\n warnedTruncatedRef.current = true;\n console.warn(\n `ResourcePage: \"${resourceName}\" has ${displayTotal} rows but only ${displayData.length} were fetched. ` +\n `Pass \\`pagination=\"server\"\\` to page through all of them, or raise \\`clientRowLimit\\` ` +\n `(no higher than the provider's \\`maxPerPage\\`, which refuses a larger page).`,\n );\n }, [clientTruncated, resourceName, displayTotal, displayData.length]);\n // ── Build ResourceActions for column render functions ──\n // `refetch` is stable; the query results around it are new each render.\n const refetchList = resourceHook.list.refetch;\n const refetchInfinite = infinite.query.refetch;\n const actions: ResourceActions<T> = useMemo(() => {\n if (isClientMode) {\n return {\n update: resourceHook.update as ResourceActions<T>[\"update\"],\n remove: resourceHook.remove,\n refetch: () => (resourceCursorPaging ? refetchInfinite() : refetchList()),\n };\n }\n return {\n update: async () => {\n throw new Error(\n \"ResourceActions.update not available in server-data mode\",\n );\n },\n remove: async () => {\n throw new Error(\n \"ResourceActions.remove not available in server-data mode\",\n );\n },\n refetch: () => {\n /* no-op in server-data mode */\n },\n };\n }, [\n isClientMode,\n resourceHook.update,\n resourceHook.remove,\n refetchList,\n resourceCursorPaging,\n refetchInfinite,\n ]);\n\n // ── Row identity ──\n const keyOf = useCallback(\n (item: T): string =>\n getKeyProp ? getKeyProp(item) : String((item as Record<string, unknown>).id ?? \"\"),\n [getKeyProp],\n );\n\n // ── The peek's row, kept live ──\n // `page.selectedItem` is a snapshot taken at click time. Re-resolving it by\n // key against the current rows means a save (which refetches) shows its\n // new values in the open peek instead of the pre-edit copy. A row missing\n // from the current rows keeps the snapshot: paging, sorting, search and\n // filters all drop rows that still exist. A confirmed delete closes the\n // peek itself (`performDelete`).\n // An empty key (rows with no `id` and no `getKey`) identifies nothing, so\n // it keeps the snapshot rather than matching the first keyless row.\n // With `urlState` the URL names the open record (`?id=`): a link opens it,\n // and Back closes it. The click-time snapshot still covers a record that\n // isn't in the current rows.\n const snapshotKey = page.selectedItem ? keyOf(page.selectedItem) : null;\n const selectedKey = urlPeek ? urlPeek.detailId : snapshotKey;\n const snapshot =\n page.selectedItem && snapshotKey === selectedKey ? page.selectedItem : null;\n const rowOrSnapshot: T | null = !selectedKey\n ? snapshot\n : (displayData.find((row) => keyOf(row) === selectedKey) ?? snapshot);\n // A linked record that isn't in the loaded rows (another page, outside the\n // search) is fetched on its own; one that can't be read shows as missing.\n const linkedId = urlPeek && selectedKey && !rowOrSnapshot ? selectedKey : null;\n const linked = useOne<T & BaseRecord>(resourceName ?? \"__noop__\", linkedId, {\n enabled: isClientMode && !!linkedId,\n });\n const selectedItem: T | null =\n rowOrSnapshot ?? (linkedId && linked.data ? (linked.data as T) : null);\n // Missing only when that is known: the fetch failed (resource mode), or\n // `data` is the whole set (data mode without server paging). A record\n // absent from one server-controlled page may be on another.\n const linkMissing =\n !!linkedId &&\n !selectedItem &&\n !isLoading &&\n (isClientMode ? linked.isError : !isServerControlled);\n const detailOpen = urlPeek ? selectedItem !== null || linkMissing : page.detailOpen;\n\n // ── Bulk selection ──\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n\n const handleToggleSelect = useCallback((id: string) => {\n setSelectedIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n }, []);\n\n const handleToggleAll = useCallback(() => {\n setSelectedIds((prev) => {\n if (prev.size === page.data.length) return new Set();\n return new Set(page.data.map(keyOf));\n });\n }, [page.data, keyOf]);\n\n const handleClearSelection = useCallback(() => {\n setSelectedIds(new Set());\n }, []);\n\n // ── Built-in form state ──\n const [formOpen, setFormOpen] = useState(false);\n const [formAction, setFormAction] = useState<\"create\" | \"edit\">(\"create\");\n const [formItem, setFormItem] = useState<T | null>(null);\n\n // Determine if built-in form should handle create/edit\n const useBuiltInCreate = allowCreate && !onCreate && isClientMode;\n const useBuiltInEdit = allowEdit && !onEdit && isClientMode;\n\n const handleCreate = useCallback(() => {\n if (useBuiltInCreate) {\n setFormAction(\"create\");\n setFormItem(null);\n setFormOpen(true);\n } else if (onCreate) {\n onCreate();\n }\n }, [useBuiltInCreate, onCreate]);\n\n // One permission check: `can`, then the deprecated per-row predicates.\n const allows = useCallback(\n (action: ResourceAction, item?: T): boolean => {\n if (can && !can(action, item)) return false;\n if (!item) return true;\n if (canEditItem && !canEditItem(item)) return false;\n if (action === \"edit\" && canEdit && !canEdit(item)) return false;\n if (action === \"delete\" && canDelete && !canDelete(item)) return false;\n return true;\n },\n [can, canEditItem, canEdit, canDelete],\n );\n\n const handleEdit = useCallback(\n (item: T) => {\n if (!allows(\"edit\", item)) return;\n if (useBuiltInEdit) {\n setFormAction(\"edit\");\n setFormItem(item);\n setFormOpen(true);\n } else if (onEdit) {\n onEdit(item);\n }\n },\n [useBuiltInEdit, onEdit, allows],\n );\n\n const closeDetail = useCallback(() => {\n page.setDetailOpen(false);\n page.setSelectedItem(null);\n urlPeek?.setDetailId(null);\n }, [page, urlPeek]);\n\n // ── Delete: built-in, confirmed, or the caller's as-is ──\n const useBuiltInDelete = !!allowDelete && !onDelete && isClientMode;\n const deleteConfirmed =\n confirmDelete === undefined ? useBuiltInDelete : !!confirmDelete;\n const deleteCopy =\n typeof confirmDelete === \"object\" ? confirmDelete : undefined;\n // The row being confirmed, kept after the dialog closes so its copy does\n // not flip to the defaults during the exit animation; `deleteOpen` is what\n // actually opens and closes it.\n const [pendingDelete, setPendingDelete] = useState<T | null>(null);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const { remove: removeRecord } = resourceHook;\n // A recycle-bin resource says so in the confirm step instead of \"can't be undone\".\n const { data: providerCaps } = useCapabilities(resourceName, {\n enabled: useBuiltInDelete && deleteConfirmed,\n });\n const deleteKind: \"recycle\" | \"permanent\" = providerCaps?.softDelete ? \"recycle\" : \"permanent\";\n\n const performDelete = useCallback(\n async (item: T) => {\n if (useBuiltInDelete) {\n // A caller-supplied `getKey` (e.g. `encodeRecordKey` for a composite\n // key) is the row's identity to the provider too; otherwise `id`.\n await removeRecord(\n (getKeyProp\n ? keyOf(item)\n : (item as Record<string, unknown>).id) as Identifier,\n );\n } else if (onDelete) {\n await onDelete(item);\n }\n closeDetail();\n onDeleted?.(item);\n },\n [\n useBuiltInDelete,\n removeRecord,\n getKeyProp,\n keyOf,\n onDelete,\n closeDetail,\n onDeleted,\n ],\n );\n\n const deleteHandler: ((item: T) => void) | undefined = !(\n useBuiltInDelete || onDelete\n )\n ? undefined\n : deleteConfirmed\n ? (item: T) => {\n setPendingDelete(item);\n setDeleteOpen(true);\n }\n : useBuiltInDelete\n ? (item: T) => {\n performDelete(item).catch((err: unknown) => {\n // useResource has already handed the failure to\n // `resourceOptions.onError`; without one, the user is told.\n if (!resourceOptions?.onError) {\n toast.error(err instanceof Error ? err.message : \"The delete failed.\");\n }\n });\n }\n : // Unconfirmed `onDelete` keeps its original contract exactly:\n // called straight from the peek, nothing closed for it.\n (item: T) => {\n // A throw or a rejected promise is reported, never left unhandled,\n // even when `onError` itself throws (the toast is the fallback).\n const report = (err: unknown) => {\n const error = err instanceof Error ? err : new Error(String(err));\n if (resourceOptions?.onError) {\n try {\n resourceOptions.onError(error);\n return;\n } catch {\n // fall through to the toast\n }\n }\n toast.error(error.message || \"The delete failed.\");\n };\n // Called right away, as before; a throw or a rejection is reported.\n try {\n Promise.resolve(onDelete?.(item)).catch(report);\n } catch (err) {\n report(err);\n }\n };\n\n // Per-row rights, evaluated against the row the peek is showing.\n const selectedEditable = !!selectedItem && allows(\"edit\", selectedItem);\n const selectedDeletable = !!selectedItem && allows(\"delete\", selectedItem);\n\n // ── Patch tableColumns to use real actions (client-side mode) ──\n const tableColumns = useMemo(() => {\n const base = isClientMode\n ? page.columns.map((col) => ({\n key: col.key,\n header: col.header ?? col.key,\n sortable: col.sortable ?? true,\n hideBelow: col.hideBelow,\n width: col.width,\n minWidth: col.minWidth,\n defaultVisible: col.defaultVisible,\n editable: col.editable,\n accessor: col.accessor,\n render: col.render\n ? (item: T, value: unknown) => col.render!(item, value, actions)\n : undefined,\n }))\n : page.tableColumns;\n if (!isServerControlled) return base;\n // The server owns the ordering. DataTable's header sort is client-side\n // and would only reorder the rows of the current page — a visibly wrong\n // answer presented as a sort — so the affordance is withdrawn rather\n // than left to lie. The DataFilters sort control drives `onSortChange`.\n return base.map((col) => ({ ...col, sortable: false }));\n }, [\n isClientMode,\n isServerControlled,\n page.columns,\n page.tableColumns,\n actions,\n ]);\n\n // ── Client mode: DataTable's header sort is `page.sort` ──\n // The primary header sort is the page's sort (and so the URL's); Shift-\n // click tie-breakers are kept here while that primary still matches, and\n // dropped when the sort changes elsewhere (the sort select, the URL).\n const [tableTieBreaks, setTableTieBreaks] = useState<{\n primary: string;\n rest: DataTableSort[];\n } | null>(null);\n const pageSort = page.sort;\n const setPageSort = page.setSort;\n const tableSort = useMemo<DataTableSort[]>(() => {\n if (!pageSort) return [];\n const primary: DataTableSort = { key: pageSort.field, dir: pageSort.order };\n const rest =\n tableTieBreaks && tableTieBreaks.primary === `${primary.key}:${primary.dir}`\n ? tableTieBreaks.rest\n : [];\n // Only the table's own columns: a sort-select option or a URL sort on a\n // field the table doesn't show still orders the rows (page.sort), but is\n // not a header sort.\n return [primary, ...rest].filter((s) => tableColumns.some((c) => c.key === s.key));\n }, [pageSort, tableTieBreaks, tableColumns]);\n const handleTableSort = useCallback(\n (next: DataTableSort[]) => {\n const [primary, ...rest] = next;\n setTableTieBreaks(primary ? { primary: `${primary.key}:${primary.dir}`, rest } : null);\n setPageSort(primary ? { field: primary.key, order: primary.dir } : null);\n },\n [setPageSort],\n );\n\n // ── Server-driven pager state, needed above for handleSearchChange's\n // already-on-page-1 check. ──\n // The pager's inputs: the caller's controlled props, or — for\n // `pagination=\"server\"` — useResource's own page state.\n const pagerPage = resourceServerPaging\n ? (resourceHook.params.pagination?.page ?? 1)\n : pageProp;\n const pagerPerPage = resourceServerPaging\n ? resourceHook.params.pagination?.perPage\n : perPageProp;\n const pagerTotal = resourceServerPaging ? displayTotal : totalProp;\n const pagerOnPageChange = resourceServerPaging\n ? resourceHook.setPage\n : onPageChange;\n const currentPage = pagerPage ?? 1;\n\n // ── `urlState`: write search, sort and (server paging) page back ──\n // Replace-only, so Back steps through opened records rather than every\n // keystroke; an unchanged URL is not rewritten.\n const writeUrl = url?.write;\n useEffect(() => {\n writeUrl?.({\n q: page.search,\n sort: page.sort,\n ...(resourceServerPaging ? { page: currentPage } : {}),\n });\n }, [writeUrl, page.search, page.sort, currentPage, resourceServerPaging]);\n\n // ── Filter changes: keep the controlled UI state, and tell the server ──\n // In client-side mode these are exactly the hook's own setters; in\n // server-controlled mode they additionally notify the caller and reset to\n // page 1, since the previous page number is meaningless against a new\n // query. The reset is skipped when already on page 1 — nothing changed,\n // so there is nothing for the caller to do.\n const handleSearchChange = useCallback(\n (value: string) => {\n page.filterConfig.onSearchChange(value);\n if (!isServerControlled) return;\n onSearchChange?.(value);\n // `pagination=\"server\"`: useResource's setSearch resets the page\n // itself once the debounce settles; resetting here too would fetch\n // page 1 of the OLD query first.\n if (currentPage !== 1 && !resourceServerPaging) onPageChange?.(1);\n },\n [\n page.filterConfig,\n isServerControlled,\n onSearchChange,\n onPageChange,\n resourceServerPaging,\n currentPage,\n ],\n );\n\n const handleSortSelect = useCallback(\n (value: string) => {\n page.filterConfig.onSortChange(value);\n if (!isServerControlled) return;\n const [field, order] = value.split(\":\");\n onSortChange?.(\n field && (order === \"asc\" || order === \"desc\")\n ? { field, order }\n : null,\n );\n // See handleSearchChange: useResource's setSort resets the page.\n if (!resourceServerPaging) onPageChange?.(1);\n },\n [\n page.filterConfig,\n isServerControlled,\n onSortChange,\n onPageChange,\n resourceServerPaging,\n ],\n );\n\n const handleClearFilters = useCallback(() => {\n page.clearFilters();\n if (!isServerControlled) return;\n // Clearing has to reach the server too — otherwise \"Clear all\" empties\n // the controls and leaves the same rows on screen.\n onSearchChange?.(\"\");\n onSortChange?.(null);\n if (!resourceServerPaging) onPageChange?.(1);\n }, [\n page,\n isServerControlled,\n onSearchChange,\n onSortChange,\n onPageChange,\n resourceServerPaging,\n ]);\n\n // ── Status-chip changes: same page-1 reset as search/sort ──\n // In server-controlled mode `useResourcePage` skips client-side status\n // filtering, so a status change only reaches the caller through\n // `onFiltersChange`. Nothing else calls `onPageChange(1)` for it, so\n // selecting a more restrictive status while on page 3 could otherwise\n // fetch page 3 of the new result set and render an empty page while the\n // matching rows sit on page 1. Guarded the same way as\n // `handleSearchChange`, and not reachable from `handleClearFilters`\n // (which already does its own single reset).\n const handleStatusChange = useCallback(\n (values: string[]) => {\n page.filterConfig.statusChips?.onChange(values);\n if (!isServerControlled) return;\n if (currentPage !== 1 && !resourceServerPaging) onPageChange?.(1);\n },\n [\n page.filterConfig,\n isServerControlled,\n onPageChange,\n resourceServerPaging,\n currentPage,\n ],\n );\n\n const statusChipsConfig = useMemo(() => {\n const base = page.filterConfig.statusChips;\n if (!base) return undefined;\n return { ...base, onChange: handleStatusChange };\n }, [page.filterConfig.statusChips, handleStatusChange]);\n\n const handlePageSizeChange = useCallback(\n (size: number) => {\n updateSetting(\"pageSize\", size);\n onPerPageChange?.(size);\n // useResource's setPerPage resets to page 1 itself.\n if (resourceServerPaging) resourceHook.setPerPage(size);\n else if (isServerControlled) onPageChange?.(1);\n },\n [\n updateSetting,\n onPerPageChange,\n resourceServerPaging,\n resourceHook.setPerPage,\n isServerControlled,\n onPageChange,\n ],\n );\n\n // ── Pagination-config error ──\n // `perPage` AND `total` are both required whenever `onPageChange` is\n // supplied. Neither can be derived from the other, and neither can be\n // derived from the rows:\n // - `perPage`: see the prop doc on `perPage`. A short final page has\n // fewer rows than the page size, so the row count of a page cannot\n // stand in for the page size.\n // - `total`: `displayTotal` falls back to `0` (Line 453). With `total`\n // omitted the range span reads \"No records\" and `Next` stays\n // disabled, while `page.data` renders rows directly below it. A\n // surface that says \"no records\" above visible records is a lie, not\n // a degraded view.\n // Rather than silently guess either one, the pager below refuses to\n // render and shows the error instead. Logged once per mount (not on\n // every render) so it's visible in a console without spamming it.\n const missingPerPage =\n isServerControlled && !!pagerOnPageChange && pagerPerPage === undefined;\n const missingTotal =\n isServerControlled && !!pagerOnPageChange && pagerTotal === undefined;\n const pagerMisconfigured = missingPerPage || missingTotal;\n const missingPagerProps = [\n ...(missingPerPage ? [\"perPage\"] : []),\n ...(missingTotal ? [\"total\"] : []),\n ];\n const loggedMissingPerPageRef = useRef(false);\n useEffect(() => {\n if (!pagerMisconfigured) return;\n if (loggedMissingPerPageRef.current) return;\n loggedMissingPerPageRef.current = true;\n console.error(\n `ResourcePage: ${missingPagerProps\n .map((name) => `\\`${name}\\``)\n .join(\" and \")} ${\n missingPagerProps.length > 1 ? \"are\" : \"is\"\n } required when \\`onPageChange\\` is supplied.`,\n );\n // `missingPagerProps` is derived from `pagerMisconfigured`'s own inputs\n // and the ref makes this run at most once, so it is not a dependency.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pagerMisconfigured]);\n\n // ── Server-driven pager math ──\n // By the time the pager renders, `pagerMisconfigured` has already gated\n // it off, so `perPageProp` and `totalProp` are both present here.\n const pagerPageSize = pagerPerPage ?? effectivePageSize;\n const totalPages = Math.max(\n 1,\n Math.ceil(displayTotal / Math.max(1, pagerPageSize)),\n );\n const rangeFrom =\n page.data.length === 0 ? 0 : (currentPage - 1) * pagerPageSize + 1;\n const rangeTo = rangeFrom === 0 ? 0 : rangeFrom + page.data.length - 1;\n\n // ── `pagination=\"server\"`: never strand the user past the last page ──\n // Deleting the last row of the last page (or any shrink in `total`) would\n // otherwise leave `page` beyond the end: the list comes back empty, the\n // shell swaps in its empty state — pager included — and there is no way\n // back. Step back to the real last page instead.\n const { setPage: setResourcePage } = resourceHook;\n const settledPage =\n !isLoading && !hasError && !resourceHook.list.isPlaceholderData;\n useEffect(() => {\n if (!resourceServerPaging || !settledPage) return;\n if (currentPage > totalPages) setResourcePage(totalPages);\n }, [resourceServerPaging, settledPage, currentPage, totalPages, setResourcePage]);\n\n // ── Detail title/subtitle values ──\n const titleOf = (item: T): string =>\n getTitle\n ? getTitle(item)\n : String((item as Record<string, unknown>)[page.titleField] ?? \"\");\n const detailTitle = selectedItem ? titleOf(selectedItem) : \"\";\n const detailSubtitle = !selectedItem\n ? undefined\n : getSubtitle\n ? getSubtitle(selectedItem)\n : page.subtitleField\n ? String(\n (selectedItem as Record<string, unknown>)[page.subtitleField] ?? \"\",\n )\n : undefined;\n\n // ── Row click handler (route mode navigates) ──\n const handleRowClick = (item: T) => {\n if (detail === \"route\") {\n // Navigate to /{resource}/{id}\n const id = (item as Record<string, unknown>).id;\n if (resourceName && id) {\n onNavigate(`/${resourceName}/${id}`);\n }\n return;\n }\n page.handleRowClick(item);\n urlPeek?.setDetailId(keyOf(item));\n };\n\n // ── Keyboard navigation (j/k, Enter, Backspace, x) ──\n // Only engaged for grid/list views: table view runs its own independent\n // client-side sort + pagination (DataTable), so a flat index into\n // `page.data` can't reliably be mapped back onto its currently-visible,\n // possibly differently-ordered rows.\n const hasBulkSelect = !!bulkActions && bulkActions.length > 0;\n // A detail panel/modal or the built-in form is covering the list. Cursor\n // keys suspend (they'd otherwise drive the list behind the overlay, and\n // Enter would re-open the row instead of activating the focused control);\n // ⌫ stays live so it can dismiss the overlay.\n const overlayOpen = detailOpen || formOpen || deleteOpen;\n const keyboardNav = useKeyboardNavigation({\n itemCount: page.data.length,\n enabled: keyboardNavigation && page.viewMode !== \"table\",\n overlayOpen,\n onOpen: (index) => {\n const item = page.data[index];\n if (item) handleRowClick(item);\n },\n onToggleSelect: hasBulkSelect\n ? (index) => {\n const item = page.data[index];\n if (item) {\n handleToggleSelect(keyOf(item));\n }\n }\n : undefined,\n // Supplied only while there is an open detail to close — the hook\n // suppresses Backspace's default whenever this is present, so leaving it\n // wired up permanently would swallow the browser/app's Backspace on\n // every grid/list page (including detail=\"none\" and detail=\"route\",\n // which never open one). `detailOpen` is only ever true for the panel\n // and modal modes, so it is the precise precondition here.\n onBack: detailOpen ? closeDetail : undefined,\n });\n\n // `pagination=\"cursor\"`: the \"Load more\" footer's state.\n const infinitePages = infinite.query.data?.pages;\n const loadMore = resourceCursorPaging\n ? {\n hasMore: infinite.hasNextPage,\n loading: infinite.isFetchingNextPage,\n load: infinite.fetchNextPage,\n loaded: infinite.rows.length,\n // Rows the latest \"Load more\" added; 0 before the first one.\n lastLoaded:\n infinitePages && infinitePages.length > 1\n ? infinitePages[infinitePages.length - 1]!.data.length\n : 0,\n }\n : undefined;\n\n // ── Create button ──\n\n return {\n actions,\n allows,\n clientTruncated,\n loadMore,\n closeDetail,\n currentPage,\n deleteConfirmed,\n deleteCopy,\n deleteHandler,\n deleteKind,\n deleteOpen,\n detailOpen,\n detailSubtitle,\n detailTitle,\n displayData,\n displayTotal,\n effectivePageSize,\n formAction,\n formItem,\n formOpen,\n handleClearFilters,\n handleClearSelection,\n handleCreate,\n handleEdit,\n handlePageSizeChange,\n handleRowClick,\n handleSearchChange,\n handleSortSelect,\n handleTableSort,\n handleToggleAll,\n handleToggleSelect,\n hasError,\n isClientMode,\n isLoading,\n isServerControlled,\n keyOf,\n keyboardNav,\n linkMissing,\n linked,\n missingPagerProps,\n page,\n pagerMisconfigured,\n pagerOnPageChange,\n pendingDelete,\n performDelete,\n rangeFrom,\n rangeTo,\n resourceHook,\n resourceServerPaging,\n selectedDeletable,\n selectedEditable,\n selectedIds,\n selectedItem,\n serverSearchFields,\n setDeleteOpen,\n setFormOpen,\n settings,\n statusChipsConfig,\n tableColumns,\n tableSort,\n titleOf,\n totalPages,\n updateSetting,\n useBuiltInCreate,\n useBuiltInDelete,\n useBuiltInEdit,\n };\n}\n\n/**\n * What {@link useResourceTable} returns: the list's rows and the handlers a\n * custom layout wires to its controls.\n *\n * @stability Beta\n */\nexport interface ResourceTableState<\n T extends Record<string, unknown> = Record<string, unknown>,\n> {\n // ── Rows ──\n /**\n * The rows to show: filtered and sorted. With `pagination=\"client\"` this\n * is every matching row (page it yourself); with server paging it is the\n * current page.\n */\n rows: T[];\n /**\n * How many rows match, across all pages. With `pagination=\"cursor\"` it is\n * the server's count while no search or status filter is active; with one,\n * only loaded rows are filtered, so it counts the loaded rows that match.\n */\n total: number;\n loading: boolean;\n /** The read failed. */\n error: boolean;\n /** True when more than `clientRowLimit` rows exist and only that many were loaded. */\n truncated: boolean;\n /**\n * `pagination=\"cursor\"`: whether more rows can be loaded, whether a page\n * is loading, and `load()` to append the next page. Undefined otherwise.\n */\n loadMore?: {\n hasMore: boolean;\n loading: boolean;\n load: () => void;\n /** Rows loaded so far. */\n loaded: number;\n /** Rows the latest load added (0 before the first \"Load more\"). */\n lastLoaded: number;\n };\n /** The columns, resolved to DataTable's format. */\n columns: DataTableColumn<T>[];\n /** Write actions for cell renderers (update, remove, …). */\n actions: ResourceActions<T>;\n /** A row's identity: `getKey`, else its `id`. */\n getKey: (item: T) => string;\n /** A row's title: `getTitle`, else its title field. */\n getTitle: (item: T) => string;\n /** The page's permission check (`can`, then the deprecated per-row predicates). */\n can: (action: ResourceAction, item?: T) => boolean;\n\n // ── Search, sort and filters ──\n search: string;\n setSearch: (value: string) => void;\n /** Sort keys, primary first. */\n sort: DataTableSort[];\n setSort: (sort: DataTableSort[]) => void;\n /** The status filter's choices, its selected values and its setter, when the list has one. */\n statusFilter:\n | { options: string[]; selected: string[]; onChange: (values: string[]) => void }\n | undefined;\n hasActiveFilters: boolean;\n clearFilters: () => void;\n\n // ── Paging (server or caller-controlled paging) ──\n /** The current page, from 1. */\n page: number;\n pageCount: number;\n pageSize: number;\n setPageSize: (size: number) => void;\n /** Go to a page; undefined when the page is not controlled (client paging). */\n setPage: ((page: number) => void) | undefined;\n /** The 1-based range of the rows shown, for \"21–40 of 95\"; 0 when empty. */\n rangeFrom: number;\n rangeTo: number;\n\n // ── Selection ──\n selectedIds: Set<string>;\n toggleSelected: (id: string) => void;\n toggleAll: () => void;\n clearSelection: () => void;\n\n // ── The open record ──\n /** The open row, kept current as the data changes. */\n openItem: T | null;\n detailOpen: boolean;\n /** What clicking a row does: opens the peek, modal or route. */\n openRow: (item: T) => void;\n closeDetail: () => void;\n /** A record linked by URL that does not exist (or was deleted). */\n openItemMissing: boolean;\n\n // ── Create and edit ──\n /** Undefined when the page cannot create. */\n create: (() => void) | undefined;\n edit: (item: T) => void;\n /** The built-in form's state, for rendering your own form. */\n form: {\n open: boolean;\n setOpen: (open: boolean) => void;\n action: \"create\" | \"edit\";\n /** The row being edited; null when creating. */\n item: T | null;\n };\n\n // ── Delete ──\n /** Delete a row: asks first when the delete is confirmed. Undefined when the page cannot delete. */\n remove: ((item: T) => void) | undefined;\n /** The confirm step's state, when `remove` asks first. */\n deleteConfirm: {\n open: boolean;\n setOpen: (open: boolean) => void;\n /** The row being confirmed (kept through the close animation). */\n item: T | null;\n /** Run the delete; rejects with the failure. */\n confirm: () => Promise<void>;\n /** \"recycle\" when the provider soft-deletes, so the copy can say it can be restored. */\n kind: \"recycle\" | \"permanent\";\n copy: ResourceDeleteConfirmCopy<T> | undefined;\n };\n}\n\n/**\n * The list behind {@link ResourcePage}, without any markup: where the rows\n * come from (a `resource` through the data provider, or `data` you pass),\n * search, sort, status filter and paging, selection, the open record (and\n * its URL state), the built-in create/edit form and confirmed delete, and\n * the row handlers. Takes the same options as `ResourcePage`.\n *\n * Use it to build a different layout over the same behaviour; `ResourcePage`\n * itself is this behaviour plus Genesis's markup.\n *\n * @stability Beta\n */\nexport function useResourceTable<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(props: ResourcePageProps<T>): ResourceTableState<T> {\n const m = useResourceTableModel<T>(props);\n const { pendingDelete, performDelete } = m;\n const confirm = useCallback(\n () => (pendingDelete ? performDelete(pendingDelete) : Promise.resolve()),\n [pendingDelete, performDelete],\n );\n return {\n rows: m.page.data,\n total:\n m.loadMore && m.page.search === \"\" && m.page.statusValue.length === 0\n ? m.displayTotal\n : m.page.total,\n loading: m.isLoading,\n error: m.hasError,\n truncated: m.clientTruncated,\n // Present only with `pagination=\"cursor\"`.\n ...(m.loadMore ? { loadMore: m.loadMore } : {}),\n columns: m.tableColumns,\n actions: m.actions,\n getKey: m.keyOf,\n getTitle: m.titleOf,\n can: m.allows,\n search: m.page.search,\n setSearch: m.handleSearchChange,\n sort: m.tableSort,\n setSort: m.handleTableSort,\n statusFilter: m.statusChipsConfig,\n hasActiveFilters: m.page.hasActiveFilters,\n clearFilters: m.handleClearFilters,\n page: m.currentPage,\n pageCount: m.totalPages,\n pageSize: m.effectivePageSize,\n setPageSize: m.handlePageSizeChange,\n setPage: m.pagerOnPageChange,\n rangeFrom: m.rangeFrom,\n rangeTo: m.rangeTo,\n selectedIds: m.selectedIds,\n toggleSelected: m.handleToggleSelect,\n toggleAll: m.handleToggleAll,\n clearSelection: m.handleClearSelection,\n openItem: m.selectedItem,\n detailOpen: m.detailOpen,\n openRow: m.handleRowClick,\n closeDetail: m.closeDetail,\n openItemMissing: m.linkMissing,\n create: m.useBuiltInCreate || props.onCreate ? m.handleCreate : undefined,\n edit: m.handleEdit,\n form: {\n open: m.formOpen,\n setOpen: m.setFormOpen,\n action: m.formAction,\n item: m.formItem,\n },\n remove: m.deleteHandler,\n deleteConfirm: {\n open: m.deleteOpen,\n setOpen: m.setDeleteOpen,\n item: pendingDelete,\n confirm,\n kind: m.deleteKind,\n copy: m.deleteCopy,\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { B as BulkAction, C as ConfirmDeleteDialog, a as ConfirmDeleteDialogProps, D as DataBulkBar, b as DataFilterConfig, c as DataFilters, d as DataGrid, e as DataPageShell, f as DataPageShellProps, g as DetailPanel, h as DetailPanelProps, F as FilterCombobox, i as FilterOption, j as ResourceAction, k as ResourceCardActions, l as ResourceDeleteConfirmCopy, m as ResourceForm, n as ResourceFormProps, o as ResourcePage, p as ResourcePageProps, q as ResourcePageSlotMap, r as ResourcePageSlotProps, s as ResourcePageSlots, R as ResourceTableState, S as SavedViewItem, t as StatusChipOption, v as StatusChipsVariant, u as useResourceTable } from '../../use-resource-table-DCVf_lEG.js';
1
+ export { B as BulkAction, C as ConfirmDeleteDialog, a as ConfirmDeleteDialogProps, D as DataBulkBar, b as DataFilterConfig, c as DataFilters, d as DataGrid, e as DataPageShell, f as DataPageShellProps, g as DetailPanel, h as DetailPanelProps, F as FilterCombobox, i as FilterOption, j as ResourceAction, k as ResourceCardActions, l as ResourceDeleteConfirmCopy, m as ResourceForm, n as ResourceFormProps, o as ResourcePage, p as ResourcePageProps, q as ResourcePageSlotMap, r as ResourcePageSlotProps, s as ResourcePageSlots, R as ResourceTableState, S as SavedViewItem, t as StatusChipOption, v as StatusChipsVariant, u as useResourceTable } from '../../use-resource-table-Dx1OZRne.js';
2
2
  export { D as DataTable, c as DataTableColumn, e as DataTableProps, d as DataTableSort, L as LinkCell, R as ResourceActions, a as ResourceColumnDef, T as ThumbnailCell } from '../../use-resource-page-DaJzuq3M.js';
3
3
  import * as React from 'react';
4
4
  import { ReactNode } from 'react';
@@ -29,8 +29,8 @@ export { EmptyState } from '../../chunk-65SPT7EA.js';
29
29
  import { Button } from '../../chunk-NWSCYLPT.js';
30
30
  import { FormField, FormLabel, FormError, FormDescription } from '../../chunk-R7QFP6EL.js';
31
31
  import '../../chunk-F47U5KNW.js';
32
- import { showUndoToast, showWriteErrorToast, useResourceTableModel } from '../../chunk-C7PQ54JI.js';
33
- export { showUndoToast, showWriteErrorToast, useResourceTable } from '../../chunk-C7PQ54JI.js';
32
+ import { showUndoToast, showWriteErrorToast, useResourceTableModel } from '../../chunk-5GOQ72WB.js';
33
+ export { showUndoToast, showWriteErrorToast, useResourceTable } from '../../chunk-5GOQ72WB.js';
34
34
  import { titleCase } from '../../chunk-RKPS7ERS.js';
35
35
  import '../../chunk-DIV4BYXX.js';
36
36
  export { useSavedViews } from '../../chunk-GCQFVMPI.js';
@@ -3459,6 +3459,7 @@ function ResourcePage(props) {
3459
3459
  actions,
3460
3460
  allows,
3461
3461
  clientTruncated,
3462
+ loadMore,
3462
3463
  closeDetail,
3463
3464
  currentPage,
3464
3465
  deleteConfirmed,
@@ -3721,7 +3722,7 @@ function ResourcePage(props) {
3721
3722
  keyboardNavigation,
3722
3723
  // The server already cut the page window; DataTable's own
3723
3724
  // pagination would slice the slice.
3724
- pagination: isServerControlled ? false : void 0,
3725
+ pagination: isServerControlled || loadMore ? false : void 0,
3725
3726
  // Client mode: the header sort is the page's sort, so it
3726
3727
  // reaches the URL and a link restores what the table shows.
3727
3728
  ...isServerControlled ? {} : { sort: tableSort, onSortChange: handleTableSort }
@@ -3737,6 +3738,30 @@ function ResourcePage(props) {
3737
3738
  },
3738
3739
  slotProps?.grid
3739
3740
  ),
3741
+ loadMore && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 px-3 py-2", children: [
3742
+ (loadMore.hasMore || loadMore.loaded > 0) && /* @__PURE__ */ jsx(
3743
+ Button,
3744
+ {
3745
+ variant: "outline",
3746
+ size: "sm",
3747
+ "aria-disabled": !loadMore.hasMore || loadMore.loading || void 0,
3748
+ "aria-busy": loadMore.loading || void 0,
3749
+ onClick: loadMore.hasMore && !loadMore.loading ? loadMore.load : void 0,
3750
+ testID: "resource-page-load-more",
3751
+ children: loadMore.loading ? "Loading\u2026" : loadMore.hasMore ? "Load more" : `All ${loadMore.loaded.toLocaleString()} loaded`
3752
+ }
3753
+ ),
3754
+ /* @__PURE__ */ jsx(
3755
+ "p",
3756
+ {
3757
+ role: "status",
3758
+ "aria-live": "polite",
3759
+ className: "sr-only",
3760
+ "data-testid": "resource-page-load-status",
3761
+ children: loadMore.lastLoaded > 0 ? `Loaded ${loadMore.lastLoaded.toLocaleString()} more. ${loadMore.loaded.toLocaleString()} shown.` : ""
3762
+ }
3763
+ )
3764
+ ] }),
3740
3765
  clientTruncated && /* @__PURE__ */ jsxs(
3741
3766
  "p",
3742
3767
  {