@marktiderman/genesis-ui 2.9.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +17 -0
- package/dist/{AppShell-LXQykEqe.d.ts → AppShell-BVr4V0UK.d.ts} +1 -110
- package/dist/PageHeader-V9lVkZtF.d.ts +114 -0
- package/dist/{chunk-S7NXD37O.js → chunk-25XEXYGC.js} +4 -4
- package/dist/chunk-25XEXYGC.js.map +1 -0
- package/dist/chunk-C7PQ54JI.js +763 -0
- package/dist/chunk-C7PQ54JI.js.map +1 -0
- package/dist/chunk-I4NBE2KL.js +16 -0
- package/dist/chunk-I4NBE2KL.js.map +1 -0
- package/dist/components/data/index.d.ts +42 -1012
- package/dist/components/data/index.js +442 -721
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/layout/index.d.ts +2 -1
- package/dist/components/patterns/index.js +2 -1
- package/dist/components/patterns/sortable-list.js +2 -1
- package/dist/hooks/index.d.ts +7 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/use-resource-page.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/{use-resource-page-BDnnCssE.d.ts → use-resource-page-DaJzuq3M.d.ts} +1 -1
- package/dist/use-resource-table-DCVf_lEG.d.ts +1113 -0
- package/package.json +2 -2
- package/dist/chunk-S7NXD37O.js.map +0 -1
|
@@ -7,13 +7,13 @@ import { Textarea } from '../../chunk-YETMISCE.js';
|
|
|
7
7
|
import { Separator } from '../../chunk-SXSCHWZU.js';
|
|
8
8
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../../chunk-XCGZPFT7.js';
|
|
9
9
|
import { Skeleton } from '../../chunk-QFM4Z2SS.js';
|
|
10
|
-
import { toast } from '../../chunk-4RAZJ6LL.js';
|
|
11
10
|
import { Progress } from '../../chunk-W5Y3LG5K.js';
|
|
12
11
|
import { Checkbox } from '../../chunk-SEBU6JCK.js';
|
|
13
12
|
import { Card, CardContent, CardHeader, CardTitle } from '../../chunk-FN3FZVUR.js';
|
|
14
13
|
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../chunk-4C4ESW6O.js';
|
|
15
14
|
import { PageLoading } from '../../chunk-YRYQXU3G.js';
|
|
16
15
|
import { Spinner } from '../../chunk-OVU3Z2MT.js';
|
|
16
|
+
import { useAnnouncer } from '../../chunk-I4NBE2KL.js';
|
|
17
17
|
import { Input } from '../../chunk-QSXVL5UB.js';
|
|
18
18
|
import '../../chunk-B2GIBUQD.js';
|
|
19
19
|
import { Badge } from '../../chunk-NPNADZHM.js';
|
|
@@ -29,27 +29,30 @@ 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 {
|
|
33
|
-
|
|
32
|
+
import { showUndoToast, showWriteErrorToast, useResourceTableModel } from '../../chunk-C7PQ54JI.js';
|
|
33
|
+
export { showUndoToast, showWriteErrorToast, useResourceTable } from '../../chunk-C7PQ54JI.js';
|
|
34
|
+
import { titleCase } from '../../chunk-RKPS7ERS.js';
|
|
35
|
+
import '../../chunk-DIV4BYXX.js';
|
|
34
36
|
export { useSavedViews } from '../../chunk-GCQFVMPI.js';
|
|
35
37
|
import '../../chunk-PKFDGGZV.js';
|
|
36
|
-
import
|
|
38
|
+
import '../../chunk-OZ7ZAYCS.js';
|
|
39
|
+
import '../../chunk-4RAZJ6LL.js';
|
|
37
40
|
export { CommandPalette, useCommandPaletteHotkey } from '../../chunk-CNTWQSUT.js';
|
|
38
41
|
import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '../../chunk-SEFK4NZV.js';
|
|
39
42
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-7XMZVISM.js';
|
|
40
43
|
import { cn } from '../../chunk-NZ5WBA4R.js';
|
|
41
|
-
import { DefaultLink
|
|
44
|
+
import { DefaultLink } from '../../chunk-WGMEVX3Y.js';
|
|
42
45
|
export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation } from '../../chunk-WGMEVX3Y.js';
|
|
43
|
-
import
|
|
46
|
+
import '../../chunk-YJ6MPS6U.js';
|
|
44
47
|
import { useIsMobile } from '../../chunk-VFO3I7PV.js';
|
|
45
|
-
import { AlertTriangle, RefreshCw, ChevronDown, Check, Search, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, ArrowRightLeft, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Plus } from 'lucide-react';
|
|
48
|
+
import { AlertTriangle, RefreshCw, ChevronDown, Check, Search, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, GripVertical, ArrowRightLeft, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Plus } from 'lucide-react';
|
|
46
49
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
47
|
-
import { useState, useRef, useEffect, useCallback, useMemo, useId, Fragment as Fragment$1 } from 'react';
|
|
50
|
+
import { useState, useRef, useEffect, useCallback, useMemo, useId, Fragment as Fragment$1, useLayoutEffect } from 'react';
|
|
48
51
|
import { useReactTable, getPaginationRowModel, getSortedRowModel, getCoreRowModel } from '@tanstack/react-table';
|
|
49
52
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
50
53
|
import { formatDistanceToNow, format } from 'date-fns';
|
|
51
54
|
import { useFieldArray, Controller, useWatch } from 'react-hook-form';
|
|
52
|
-
import { useResource, useWizard, useResourceForm, useOne,
|
|
55
|
+
import { useResource, useWizard, useResourceForm, useOne, buildArrayItemDefaults } from '@marktiderman/genesis-core/hooks';
|
|
53
56
|
import { useDataProvider, isDataReadError } from '@marktiderman/genesis-core/provider';
|
|
54
57
|
import { useDropzone } from 'react-dropzone';
|
|
55
58
|
|
|
@@ -139,29 +142,43 @@ function DataPageShell({
|
|
|
139
142
|
/* @__PURE__ */ jsx(Header, { ...sharedHeaderProps, options: baseHeaderOptions }),
|
|
140
143
|
filters
|
|
141
144
|
] }),
|
|
142
|
-
isLoading ?
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
children: Array.from({ length:
|
|
145
|
+
isLoading ? (
|
|
146
|
+
// Shaped like the view it stands in for, so a list inside a panel or
|
|
147
|
+
// tab doesn't reserve a page of card placeholders.
|
|
148
|
+
/* @__PURE__ */ jsxs("div", { "data-testid": "page-loader", role: "status", "aria-busy": "true", children: [
|
|
149
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading\u2026" }),
|
|
150
|
+
viewMode === "grid" ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
148
151
|
Skeleton,
|
|
149
152
|
{
|
|
150
|
-
className: "h-
|
|
153
|
+
className: "h-32 rounded-xl",
|
|
151
154
|
style: { animationDelay: `${i * 75}ms` }
|
|
152
155
|
},
|
|
153
156
|
i
|
|
154
|
-
))
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
" Retry"
|
|
157
|
+
)) }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
158
|
+
Skeleton,
|
|
159
|
+
{
|
|
160
|
+
className: "h-10 rounded-md",
|
|
161
|
+
style: { animationDelay: `${i * 75}ms` }
|
|
162
|
+
},
|
|
163
|
+
i
|
|
164
|
+
)) })
|
|
163
165
|
] })
|
|
164
|
-
|
|
166
|
+
) : hasError ? /* @__PURE__ */ jsxs(
|
|
167
|
+
"div",
|
|
168
|
+
{
|
|
169
|
+
role: "alert",
|
|
170
|
+
className: "flex flex-col items-center justify-center py-16 text-center",
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3", "aria-hidden": true }),
|
|
173
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: "Something went wrong" }),
|
|
174
|
+
/* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage2 || `Failed to load ${entity}s. Please try again.` }),
|
|
175
|
+
onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
|
|
176
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-4 w-4", "aria-hidden": true }),
|
|
177
|
+
" Retry"
|
|
178
|
+
] })
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
) : isEmpty ? /* @__PURE__ */ jsx(
|
|
165
182
|
EmptyState,
|
|
166
183
|
{
|
|
167
184
|
icon: emptyIcon,
|
|
@@ -1140,8 +1157,8 @@ function DataTable({
|
|
|
1140
1157
|
setCurrentPage(0);
|
|
1141
1158
|
}, [items.length, currentPageSize]);
|
|
1142
1159
|
const sorting = useMemo(
|
|
1143
|
-
() => sorts.map((s) => ({ id: s.key, desc: s.dir === "desc" })),
|
|
1144
|
-
[sorts]
|
|
1160
|
+
() => sorts.filter((s) => columns.some((c) => c.key === s.key)).map((s) => ({ id: s.key, desc: s.dir === "desc" })),
|
|
1161
|
+
[sorts, columns]
|
|
1145
1162
|
);
|
|
1146
1163
|
const tanstackColumns = useMemo(
|
|
1147
1164
|
() => columns.map((col) => ({
|
|
@@ -1195,7 +1212,11 @@ function DataTable({
|
|
|
1195
1212
|
requestAnimationFrame(() => focusRowAt(bodyRef.current, target));
|
|
1196
1213
|
};
|
|
1197
1214
|
const openKey = !rowKeys ? null : openColumn !== void 0 ? openColumn : activeColumns.find((c) => !c.editable)?.key ?? null;
|
|
1198
|
-
const
|
|
1215
|
+
const renderedItems = virtualRows ? virtualRows.map((vr) => displayItems[vr.index]).filter(Boolean) : displayItems;
|
|
1216
|
+
const scrollTop = rowVirtualizer.scrollOffset ?? 0;
|
|
1217
|
+
const firstInView = virtualRows ? virtualRows.find((vr) => vr.start >= scrollTop) ?? virtualRows.find((vr) => vr.end > scrollTop) ?? virtualRows[0] : void 0;
|
|
1218
|
+
const fallbackItem = firstInView ? displayItems[firstInView.index] : renderedItems[0];
|
|
1219
|
+
const tabStopKey = activeRowKey && renderedItems.some((it) => getKey(it) === activeRowKey) ? activeRowKey : fallbackItem ? getKey(fallbackItem) : null;
|
|
1199
1220
|
const rowLabel = (item, id) => {
|
|
1200
1221
|
const col = activeColumns.find((c) => c.key === (openKey ?? activeColumns[0]?.key));
|
|
1201
1222
|
const value = col ? (col.accessor ?? ((it) => it[col.key]))(item) : void 0;
|
|
@@ -1789,6 +1810,7 @@ var statusColorMap = {
|
|
|
1789
1810
|
primary: { color: "text-primary", bgColor: "bg-primary/15" }
|
|
1790
1811
|
};
|
|
1791
1812
|
var DRAG_TYPE = "application/x-genesis-kanban-item";
|
|
1813
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
1792
1814
|
function KanbanBoard({
|
|
1793
1815
|
columns,
|
|
1794
1816
|
renderCard,
|
|
@@ -1801,112 +1823,301 @@ function KanbanBoard({
|
|
|
1801
1823
|
}) {
|
|
1802
1824
|
const [dragging, setDragging] = useState(null);
|
|
1803
1825
|
const [over, setOver] = useState(null);
|
|
1826
|
+
const [grabbed, setGrabbed] = useState(null);
|
|
1827
|
+
const { message: announcement, announce } = useAnnouncer();
|
|
1828
|
+
const handles = useRef(/* @__PURE__ */ new Map());
|
|
1829
|
+
const refocus = useRef(null);
|
|
1830
|
+
const instructionsId = useId();
|
|
1804
1831
|
const keyOf = (item, index) => getKey ? getKey(item) : String(index);
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1832
|
+
const movable = Boolean(onMoveItem && getKey);
|
|
1833
|
+
const find = (key) => {
|
|
1834
|
+
if (!getKey) return null;
|
|
1835
|
+
for (const column of columns) {
|
|
1836
|
+
const item = column.items.find((i) => getKey(i) === key);
|
|
1837
|
+
if (item !== void 0) return { item, column };
|
|
1838
|
+
}
|
|
1839
|
+
return null;
|
|
1840
|
+
};
|
|
1841
|
+
const labelOf = (key) => {
|
|
1842
|
+
const found = find(key);
|
|
1843
|
+
return found && getItemLabel ? getItemLabel(found.item) : "Card";
|
|
1844
|
+
};
|
|
1845
|
+
const columnLabel = (key) => columns.find((c) => c.key === key)?.label ?? key;
|
|
1846
|
+
const move = (item, to, name) => {
|
|
1847
|
+
let result;
|
|
1848
|
+
try {
|
|
1849
|
+
result = onMoveItem?.(item, to);
|
|
1850
|
+
} catch (error) {
|
|
1851
|
+
result = Promise.reject(error);
|
|
1852
|
+
}
|
|
1853
|
+
void Promise.resolve(result).then(
|
|
1854
|
+
() => announce(`${name} moved to ${columnLabel(to)}.`),
|
|
1855
|
+
() => announce(`Couldn\u2019t move ${name}.`)
|
|
1856
|
+
);
|
|
1857
|
+
};
|
|
1858
|
+
const moveKey = (key, to) => {
|
|
1859
|
+
const found = find(key);
|
|
1860
|
+
if (!found || found.column.key === to) return;
|
|
1861
|
+
move(found.item, to, labelOf(key));
|
|
1808
1862
|
};
|
|
1809
1863
|
const onDrop = (event, to) => {
|
|
1810
1864
|
event.preventDefault();
|
|
1811
1865
|
setOver(null);
|
|
1812
1866
|
const key = event.dataTransfer.getData(DRAG_TYPE) || dragging?.key;
|
|
1813
1867
|
setDragging(null);
|
|
1814
|
-
if (
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1868
|
+
if (key) moveKey(key, to);
|
|
1869
|
+
};
|
|
1870
|
+
const pickUp = (key, from) => {
|
|
1871
|
+
setGrabbed({ key, from, target: from });
|
|
1872
|
+
announce(
|
|
1873
|
+
`${labelOf(key)} picked up in ${columnLabel(from)}. Use the left and right arrow keys to choose a column, Space to drop, Escape to cancel.`
|
|
1874
|
+
);
|
|
1875
|
+
};
|
|
1876
|
+
const aim = (index) => {
|
|
1877
|
+
if (!grabbed) return;
|
|
1878
|
+
const column = columns[Math.max(0, Math.min(columns.length - 1, index))];
|
|
1879
|
+
if (!column || column.key === grabbed.target) return;
|
|
1880
|
+
setGrabbed({ ...grabbed, target: column.key });
|
|
1881
|
+
announce(`Over ${column.label}, column ${columns.indexOf(column) + 1} of ${columns.length}.`);
|
|
1882
|
+
};
|
|
1883
|
+
const drop = () => {
|
|
1884
|
+
if (!grabbed) return;
|
|
1885
|
+
setGrabbed(null);
|
|
1886
|
+
if (grabbed.target === grabbed.from) {
|
|
1887
|
+
announce(`${labelOf(grabbed.key)} dropped back in ${columnLabel(grabbed.from)}.`);
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
refocus.current = grabbed.key;
|
|
1891
|
+
moveKey(grabbed.key, grabbed.target);
|
|
1892
|
+
};
|
|
1893
|
+
const cancel = () => {
|
|
1894
|
+
if (!grabbed) return;
|
|
1895
|
+
setGrabbed(null);
|
|
1896
|
+
announce(`Move cancelled. ${labelOf(grabbed.key)} stays in ${columnLabel(grabbed.from)}.`);
|
|
1897
|
+
};
|
|
1898
|
+
useEffect(() => {
|
|
1899
|
+
if (!grabbed) return;
|
|
1900
|
+
const onKey = (event) => {
|
|
1901
|
+
if (event.key !== "Escape") return;
|
|
1902
|
+
event.preventDefault();
|
|
1903
|
+
event.stopPropagation();
|
|
1904
|
+
cancel();
|
|
1905
|
+
};
|
|
1906
|
+
window.addEventListener("keydown", onKey, true);
|
|
1907
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
1908
|
+
});
|
|
1909
|
+
useEffect(() => {
|
|
1910
|
+
if (grabbed && (!movable || !find(grabbed.key))) setGrabbed(null);
|
|
1911
|
+
if (dragging && (!movable || !find(dragging.key))) {
|
|
1912
|
+
setDragging(null);
|
|
1913
|
+
setOver(null);
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
useIsomorphicLayoutEffect(() => {
|
|
1917
|
+
const key = refocus.current;
|
|
1918
|
+
if (!key) return;
|
|
1919
|
+
const handle = handles.current.get(key);
|
|
1920
|
+
if (!handle) return;
|
|
1921
|
+
if (handle.isConnected && document.activeElement === handle) {
|
|
1922
|
+
refocus.current = null;
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const active = document.activeElement;
|
|
1926
|
+
if (active === null || active === document.body) {
|
|
1927
|
+
handle.focus();
|
|
1928
|
+
refocus.current = null;
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
const onHandleKeyDown = (event, key, from) => {
|
|
1932
|
+
const delta = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
|
|
1933
|
+
if (grabbed?.key === key) {
|
|
1934
|
+
const index = columns.findIndex((c) => c.key === grabbed.target);
|
|
1935
|
+
if (delta) {
|
|
1936
|
+
event.preventDefault();
|
|
1937
|
+
aim(index + delta);
|
|
1938
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
1939
|
+
event.preventDefault();
|
|
1940
|
+
aim(event.key === "Home" ? 0 : columns.length - 1);
|
|
1941
|
+
}
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
if (delta && event.altKey && !grabbed) {
|
|
1945
|
+
event.preventDefault();
|
|
1946
|
+
const to = columns[columns.findIndex((c) => c.key === from) + delta];
|
|
1947
|
+
if (to) {
|
|
1948
|
+
refocus.current = key;
|
|
1949
|
+
moveKey(key, to.key);
|
|
1820
1950
|
}
|
|
1821
1951
|
}
|
|
1822
1952
|
};
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1953
|
+
const onHandleClick = (event, key, from) => {
|
|
1954
|
+
if (event.detail !== 0) return;
|
|
1955
|
+
if (grabbed?.key === key) drop();
|
|
1956
|
+
else if (!grabbed) pickUp(key, from);
|
|
1957
|
+
};
|
|
1958
|
+
const columnAt = (x, y) => (typeof document.elementFromPoint === "function" ? document.elementFromPoint(x, y) : null)?.closest("[data-kanban-column]")?.dataset.kanbanColumn ?? null;
|
|
1959
|
+
const onHandlePointerDown = (event, key, from) => {
|
|
1960
|
+
if (event.pointerType === "mouse" || grabbed || dragging) return;
|
|
1961
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1962
|
+
setDragging({ key, from, pointer: event.pointerId });
|
|
1963
|
+
announce(`${labelOf(key)} picked up in ${columnLabel(from)}.`);
|
|
1964
|
+
};
|
|
1965
|
+
const onHandlePointerMove = (event) => {
|
|
1966
|
+
if (dragging?.pointer !== event.pointerId) return;
|
|
1967
|
+
const to = columnAt(event.clientX, event.clientY);
|
|
1968
|
+
if (to !== over) setOver(to);
|
|
1969
|
+
};
|
|
1970
|
+
const onHandlePointerUp = (event) => {
|
|
1971
|
+
if (dragging?.pointer !== event.pointerId) return;
|
|
1972
|
+
const to = columnAt(event.clientX, event.clientY) ?? over;
|
|
1973
|
+
const { key, from } = dragging;
|
|
1974
|
+
setDragging(null);
|
|
1975
|
+
setOver(null);
|
|
1976
|
+
if (to && to !== from) moveKey(key, to);
|
|
1977
|
+
else announce(`${labelOf(key)} dropped back in ${columnLabel(from)}.`);
|
|
1978
|
+
};
|
|
1979
|
+
const onHandlePointerCancel = (event) => {
|
|
1980
|
+
if (dragging?.pointer !== event.pointerId) return;
|
|
1981
|
+
setDragging(null);
|
|
1982
|
+
setOver(null);
|
|
1983
|
+
};
|
|
1984
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1985
|
+
/* @__PURE__ */ jsx(
|
|
1986
|
+
"div",
|
|
1987
|
+
{
|
|
1988
|
+
className: cn(
|
|
1989
|
+
"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
|
|
1990
|
+
className
|
|
1991
|
+
),
|
|
1992
|
+
children: columns.map((column) => {
|
|
1993
|
+
const resolved = column.status ? statusColorMap[column.status] : { color: column.color ?? "", bgColor: column.bgColor ?? "" };
|
|
1994
|
+
return /* @__PURE__ */ jsxs(
|
|
1995
|
+
"section",
|
|
1996
|
+
{
|
|
1997
|
+
"aria-label": column.label,
|
|
1998
|
+
"data-kanban-column": column.key,
|
|
1999
|
+
className: cn(
|
|
2000
|
+
"flex flex-col gap-3 rounded-lg",
|
|
2001
|
+
movable && (dragging && over === column.key && dragging.from !== column.key || grabbed && grabbed.target === column.key && grabbed.from !== column.key) && "bg-accent/40 ring-2 ring-primary/40"
|
|
2002
|
+
),
|
|
2003
|
+
onDragOver: movable ? (e) => {
|
|
2004
|
+
if (!dragging || dragging.pointer !== void 0) return;
|
|
2005
|
+
e.preventDefault();
|
|
2006
|
+
e.dataTransfer.dropEffect = "move";
|
|
2007
|
+
if (over !== column.key) setOver(column.key);
|
|
2008
|
+
} : void 0,
|
|
2009
|
+
onDragLeave: movable ? (e) => {
|
|
2010
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setOver(null);
|
|
2011
|
+
} : void 0,
|
|
2012
|
+
onDrop: movable ? (e) => onDrop(e, column.key) : void 0,
|
|
2013
|
+
children: [
|
|
2014
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2015
|
+
/* @__PURE__ */ jsx("span", { className: cn("text-sm font-semibold", resolved.color), children: column.label }),
|
|
2016
|
+
/* @__PURE__ */ jsx(
|
|
2017
|
+
Badge,
|
|
2018
|
+
{
|
|
2019
|
+
variant: "secondary",
|
|
2020
|
+
className: cn("text-[10px] px-1.5 py-0", resolved.bgColor),
|
|
2021
|
+
children: column.total ?? column.items.length
|
|
2022
|
+
}
|
|
2023
|
+
)
|
|
2024
|
+
] }),
|
|
2025
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-h-[80px]", children: column.items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-lg border border-dashed border-border p-6 text-xs text-muted-foreground", children: emptyMessage }) : column.items.map((item, i) => {
|
|
2026
|
+
const key = keyOf(item, i);
|
|
2027
|
+
const name = getItemLabel?.(item) ?? "card";
|
|
2028
|
+
const isGrabbed = grabbed?.key === key;
|
|
2029
|
+
return /* @__PURE__ */ jsxs(
|
|
2030
|
+
"div",
|
|
2031
|
+
{
|
|
2032
|
+
className: cn(
|
|
2033
|
+
"animate-fade-in",
|
|
2034
|
+
movable && "group relative rounded-lg",
|
|
2035
|
+
dragging?.key === key && "opacity-50",
|
|
2036
|
+
isGrabbed && "ring-2 ring-primary/50"
|
|
2037
|
+
),
|
|
2038
|
+
style: { animationDelay: `${i * 30}ms`, animationFillMode: "both" },
|
|
2039
|
+
draggable: movable || void 0,
|
|
2040
|
+
onDragStart: movable ? (e) => {
|
|
2041
|
+
e.dataTransfer.setData(DRAG_TYPE, key);
|
|
2042
|
+
e.dataTransfer.effectAllowed = "move";
|
|
2043
|
+
setDragging({ key, from: column.key });
|
|
2044
|
+
} : void 0,
|
|
2045
|
+
onDragEnd: movable ? () => {
|
|
2046
|
+
setDragging(null);
|
|
2047
|
+
setOver(null);
|
|
2048
|
+
} : void 0,
|
|
2049
|
+
children: [
|
|
2050
|
+
renderCard(item),
|
|
2051
|
+
movable && columns.length > 1 && /* @__PURE__ */ jsx(
|
|
2052
|
+
"button",
|
|
1886
2053
|
{
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2054
|
+
type: "button",
|
|
2055
|
+
ref: (node) => {
|
|
2056
|
+
if (node) handles.current.set(key, node);
|
|
2057
|
+
else handles.current.delete(key);
|
|
2058
|
+
},
|
|
2059
|
+
"aria-label": `Pick up ${name}`,
|
|
2060
|
+
"aria-describedby": instructionsId,
|
|
2061
|
+
"data-grabbed": isGrabbed || void 0,
|
|
2062
|
+
className: cn(
|
|
2063
|
+
"absolute right-8 top-1.5 cursor-grab touch-none rounded p-1 text-muted-foreground",
|
|
2064
|
+
"hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2065
|
+
isGrabbed && "bg-accent text-foreground"
|
|
2066
|
+
),
|
|
2067
|
+
onClick: (e) => onHandleClick(e, key, column.key),
|
|
2068
|
+
onKeyDown: (e) => onHandleKeyDown(e, key, column.key),
|
|
2069
|
+
onBlur: () => {
|
|
2070
|
+
if (isGrabbed) cancel();
|
|
2071
|
+
},
|
|
2072
|
+
onPointerDown: (e) => onHandlePointerDown(e, key, column.key),
|
|
2073
|
+
onPointerMove: onHandlePointerMove,
|
|
2074
|
+
onPointerUp: onHandlePointerUp,
|
|
2075
|
+
onPointerCancel: onHandlePointerCancel,
|
|
2076
|
+
children: /* @__PURE__ */ jsx(GripVertical, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
1890
2077
|
}
|
|
1891
2078
|
),
|
|
1892
|
-
/* @__PURE__ */ jsxs(
|
|
1893
|
-
/* @__PURE__ */ jsx(
|
|
1894
|
-
|
|
2079
|
+
movable && columns.length > 1 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2080
|
+
/* @__PURE__ */ jsx(
|
|
2081
|
+
DropdownMenuTrigger,
|
|
2082
|
+
{
|
|
2083
|
+
"aria-label": `Move ${name}`,
|
|
2084
|
+
className: "absolute right-1.5 top-1.5 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2085
|
+
children: /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
2086
|
+
}
|
|
2087
|
+
),
|
|
2088
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
2089
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Move to" }),
|
|
2090
|
+
columns.filter((c) => c.key !== column.key).map((c) => /* @__PURE__ */ jsx(
|
|
2091
|
+
DropdownMenuItem,
|
|
2092
|
+
{
|
|
2093
|
+
onSelect: () => {
|
|
2094
|
+
refocus.current = key;
|
|
2095
|
+
move(item, c.key, getItemLabel?.(item) ?? "Card");
|
|
2096
|
+
},
|
|
2097
|
+
children: c.label
|
|
2098
|
+
},
|
|
2099
|
+
c.key
|
|
2100
|
+
))
|
|
2101
|
+
] })
|
|
1895
2102
|
] })
|
|
1896
|
-
]
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
)
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
)
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
|
|
2103
|
+
]
|
|
2104
|
+
},
|
|
2105
|
+
key
|
|
2106
|
+
);
|
|
2107
|
+
}) }),
|
|
2108
|
+
renderColumnFooter?.(column)
|
|
2109
|
+
]
|
|
2110
|
+
},
|
|
2111
|
+
column.key
|
|
2112
|
+
);
|
|
2113
|
+
})
|
|
2114
|
+
}
|
|
2115
|
+
),
|
|
2116
|
+
movable && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2117
|
+
/* @__PURE__ */ jsx("p", { id: instructionsId, hidden: true, children: "Space or Enter picks the card up. Left and right arrows, Home and End choose a column. Space or Enter drops it, Escape puts it back. Alt with an arrow moves it one column." }),
|
|
2118
|
+
/* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement })
|
|
2119
|
+
] })
|
|
2120
|
+
] });
|
|
1910
2121
|
}
|
|
1911
2122
|
function DataList({
|
|
1912
2123
|
items,
|
|
@@ -2122,39 +2333,6 @@ function DetailPanel({
|
|
|
2122
2333
|
}
|
|
2123
2334
|
);
|
|
2124
2335
|
}
|
|
2125
|
-
|
|
2126
|
-
// src/components/data/undo-toast.ts
|
|
2127
|
-
var DONE = {
|
|
2128
|
-
create: "Created.",
|
|
2129
|
-
update: "Saved.",
|
|
2130
|
-
delete: "Deleted."
|
|
2131
|
-
};
|
|
2132
|
-
function showUndoToast(write) {
|
|
2133
|
-
toast(DONE[write.kind], {
|
|
2134
|
-
duration: write.delayMs,
|
|
2135
|
-
action: {
|
|
2136
|
-
label: "Undo",
|
|
2137
|
-
onClick: () => {
|
|
2138
|
-
void write.undo().then((undone) => {
|
|
2139
|
-
if (!undone && write.strategy === "delay") {
|
|
2140
|
-
toast("Already saved. It can no longer be undone.");
|
|
2141
|
-
}
|
|
2142
|
-
});
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
});
|
|
2146
|
-
}
|
|
2147
|
-
function showWriteErrorToast(error) {
|
|
2148
|
-
toast.error(error.message || "The change could not be saved.");
|
|
2149
|
-
}
|
|
2150
|
-
function writeModeOptions(resourceOptions, mutationMode) {
|
|
2151
|
-
const mode = resourceOptions?.mutationMode ?? mutationMode ?? "pessimistic";
|
|
2152
|
-
return {
|
|
2153
|
-
mutationMode: mode,
|
|
2154
|
-
onUndoable: resourceOptions?.onUndoable ?? showUndoToast,
|
|
2155
|
-
onError: resourceOptions?.onError ?? (mode === "pessimistic" ? void 0 : showWriteErrorToast)
|
|
2156
|
-
};
|
|
2157
|
-
}
|
|
2158
2336
|
function RelationField({
|
|
2159
2337
|
field,
|
|
2160
2338
|
value,
|
|
@@ -3087,6 +3265,8 @@ function ResourceForm(props) {
|
|
|
3087
3265
|
prefillFromUrl,
|
|
3088
3266
|
autoPlaceholder,
|
|
3089
3267
|
warnWhenUnsaved,
|
|
3268
|
+
// A closed dialog is not touched by a save that fails later.
|
|
3269
|
+
visible: open,
|
|
3090
3270
|
mutationMode,
|
|
3091
3271
|
undoDelayMs,
|
|
3092
3272
|
onUndoable: onUndoable ?? showUndoToast
|
|
@@ -3218,589 +3398,130 @@ function ConfirmDeleteDialog({
|
|
|
3218
3398
|
}
|
|
3219
3399
|
function ResourcePage(props) {
|
|
3220
3400
|
const {
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
total: totalProp,
|
|
3224
|
-
pagination = "client",
|
|
3225
|
-
clientRowLimit = 1e3,
|
|
3226
|
-
title,
|
|
3227
|
-
subtitle,
|
|
3228
|
-
icon,
|
|
3229
|
-
columns: columnsProp,
|
|
3230
|
-
getKey: getKeyProp,
|
|
3231
|
-
detail = "panel",
|
|
3232
|
-
detailWidth = "md",
|
|
3233
|
-
titleField: titleFieldProp,
|
|
3234
|
-
subtitleField: subtitleFieldProp,
|
|
3235
|
-
getTitle,
|
|
3236
|
-
getSubtitle,
|
|
3237
|
-
renderDetail: renderDetailProp,
|
|
3238
|
-
detailFields,
|
|
3239
|
-
statusFilter,
|
|
3240
|
-
searchFields,
|
|
3241
|
-
searchPlaceholder,
|
|
3242
|
-
initialFilters,
|
|
3243
|
-
onFiltersChange,
|
|
3244
|
-
filters: filtersProp,
|
|
3245
|
-
page: pageProp,
|
|
3246
|
-
perPage: perPageProp,
|
|
3247
|
-
onPageChange,
|
|
3248
|
-
onPerPageChange,
|
|
3249
|
-
onSortChange,
|
|
3250
|
-
onSearchChange,
|
|
3251
|
-
loading: loadingProp,
|
|
3252
|
-
error: errorProp,
|
|
3253
|
-
errorMessage: errorMessage2,
|
|
3254
|
-
onRetry: onRetryProp,
|
|
3255
|
-
savedViews,
|
|
3256
|
-
onLoadView,
|
|
3257
|
-
onSaveView,
|
|
3258
|
-
onDeleteView,
|
|
3401
|
+
bulkActions,
|
|
3402
|
+
can,
|
|
3259
3403
|
canSaveViews,
|
|
3404
|
+
capabilities,
|
|
3405
|
+
className,
|
|
3260
3406
|
columnVisibility,
|
|
3261
3407
|
columnVisibilityKey,
|
|
3262
|
-
resizableColumns,
|
|
3263
|
-
resizeKey,
|
|
3264
|
-
stickyHeader,
|
|
3265
|
-
stickyFirstColumn,
|
|
3266
|
-
pageSizeOptions,
|
|
3267
|
-
onCreate,
|
|
3268
|
-
onEdit,
|
|
3269
|
-
onDelete,
|
|
3270
|
-
can,
|
|
3271
|
-
canEditItem,
|
|
3272
3408
|
createLabel = "New",
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3409
|
+
detail = "panel",
|
|
3410
|
+
detailFields,
|
|
3411
|
+
detailWidth = "md",
|
|
3412
|
+
emptyDescription,
|
|
3413
|
+
emptyIcon,
|
|
3414
|
+
emptyMessage,
|
|
3415
|
+
emptyState,
|
|
3416
|
+
errorMessage: errorMessage2,
|
|
3417
|
+
filters: filtersProp,
|
|
3281
3418
|
formFields,
|
|
3282
|
-
|
|
3419
|
+
formLayout: formLayoutProp,
|
|
3283
3420
|
formMode,
|
|
3284
3421
|
formSteps,
|
|
3285
|
-
|
|
3422
|
+
gridCols: gridColsProp,
|
|
3423
|
+
header: headerMode = "page",
|
|
3424
|
+
icon,
|
|
3425
|
+
keyboardNavigation = true,
|
|
3286
3426
|
mutationMode: mutationModeProp,
|
|
3287
|
-
|
|
3288
|
-
|
|
3427
|
+
onCreate,
|
|
3428
|
+
onDelete,
|
|
3429
|
+
onDeleteView,
|
|
3430
|
+
onEdit,
|
|
3431
|
+
onLoadView,
|
|
3432
|
+
onPageChange,
|
|
3433
|
+
onRetry: onRetryProp,
|
|
3434
|
+
onSaveView,
|
|
3435
|
+
onSearchChange,
|
|
3436
|
+
onSortChange,
|
|
3289
3437
|
openOnCardClick = false,
|
|
3438
|
+
pageSizeOptions,
|
|
3439
|
+
pagination = "client",
|
|
3440
|
+
renderCard: renderCardProp,
|
|
3441
|
+
renderDetail: renderDetailProp,
|
|
3442
|
+
renderForm: renderFormProp,
|
|
3290
3443
|
renderTable: renderTableProp,
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
emptyMessage,
|
|
3298
|
-
emptyDescription,
|
|
3299
|
-
header: headerMode = "page",
|
|
3300
|
-
resourceOptions,
|
|
3301
|
-
urlState: urlStateProp,
|
|
3302
|
-
defaultView = "table",
|
|
3303
|
-
onNavigate = defaultNavigate,
|
|
3304
|
-
viewSettingsKey: viewSettingsKeyProp,
|
|
3305
|
-
slots,
|
|
3444
|
+
resizableColumns,
|
|
3445
|
+
resizeKey,
|
|
3446
|
+
resource: resourceName,
|
|
3447
|
+
savedViews,
|
|
3448
|
+
searchPlaceholder,
|
|
3449
|
+
secondaryActions,
|
|
3306
3450
|
slotProps,
|
|
3307
|
-
|
|
3451
|
+
slots,
|
|
3452
|
+
stickyFirstColumn,
|
|
3453
|
+
stickyHeader,
|
|
3454
|
+
subtitle,
|
|
3455
|
+
title,
|
|
3456
|
+
toolbar
|
|
3308
3457
|
} = props;
|
|
3309
|
-
const isClientMode = !!resourceName;
|
|
3310
|
-
const resourceServerPaging = isClientMode && pagination === "server";
|
|
3311
|
-
const url = useResourceUrlState(
|
|
3312
|
-
urlStateProp ? "setSearchParams" in urlStateProp ? { searchParams: urlStateProp } : urlStateProp : void 0
|
|
3313
|
-
);
|
|
3314
|
-
const [urlSeed] = useState(() => {
|
|
3315
|
-
if (!url) return null;
|
|
3316
|
-
const sortable = !url.sort || !columnsProp ? true : columnsProp.some(
|
|
3317
|
-
(col) => typeof col === "string" ? col === url.sort.field : col.key === url.sort.field && col.sortable !== false
|
|
3318
|
-
);
|
|
3319
|
-
return { q: url.q, sort: sortable ? url.sort : null, page: url.page };
|
|
3320
|
-
});
|
|
3321
|
-
const urlPeek = detail === "panel" || detail === "modal" ? url : void 0;
|
|
3322
|
-
const urlSortKey = urlSeed?.sort ? `${urlSeed.sort.field}:${urlSeed.sort.order}` : "";
|
|
3323
|
-
const isServerControlled = resourceServerPaging || pageProp !== void 0 || perPageProp !== void 0 || onPageChange !== void 0 || onPerPageChange !== void 0 || onSortChange !== void 0 || onSearchChange !== void 0;
|
|
3324
|
-
const viewSettingsKey = viewSettingsKeyProp ?? resourceName ?? title.toLowerCase().replace(/\s+/g, "-");
|
|
3325
|
-
const { settings, updateSetting } = useViewSettings(viewSettingsKey);
|
|
3326
|
-
const serverSearchFields = resourceOptions?.searchFields ?? searchFields;
|
|
3327
|
-
const resourceHook = useResource(
|
|
3328
|
-
resourceName ?? "__noop__",
|
|
3329
|
-
isClientMode ? {
|
|
3330
|
-
...resourceOptions,
|
|
3331
|
-
// Client paging fetches everything it can in one request; server
|
|
3332
|
-
// paging starts at the user's stored page size. An explicit
|
|
3333
|
-
// `defaultPerPage` wins over both.
|
|
3334
|
-
defaultPerPage: resourceOptions?.defaultPerPage ?? (resourceServerPaging ? perPageProp ?? settings.pageSize : clientRowLimit),
|
|
3335
|
-
searchFields: resourceServerPaging ? serverSearchFields : resourceOptions?.searchFields,
|
|
3336
|
-
// Paging, searching and sorting change the query key; keep the
|
|
3337
|
-
// current page on screen while the next one loads.
|
|
3338
|
-
keepPreviousData: resourceOptions?.keepPreviousData ?? resourceServerPaging,
|
|
3339
|
-
// Start where the URL says, so restoring a link doesn't push a
|
|
3340
|
-
// search or sort that resets the page to 1.
|
|
3341
|
-
...resourceServerPaging && urlSeed ? {
|
|
3342
|
-
defaultSearch: urlSeed.q,
|
|
3343
|
-
...urlSeed.page ? { defaultPage: urlSeed.page } : {},
|
|
3344
|
-
...urlSeed.sort ? { defaultSort: urlSeed.sort } : {}
|
|
3345
|
-
} : {},
|
|
3346
|
-
...writeModeOptions(resourceOptions, mutationModeProp)
|
|
3347
|
-
} : { enabled: false }
|
|
3348
|
-
);
|
|
3349
|
-
const effectivePageSize = resourceServerPaging ? resourceHook.params.pagination?.perPage ?? settings.pageSize : perPageProp ?? settings.pageSize;
|
|
3350
|
-
const displayData = isClientMode ? resourceHook.list.data?.data ?? [] : dataProp ?? [];
|
|
3351
|
-
const displayTotal = isClientMode ? resourceHook.list.data?.total ?? 0 : totalProp ?? 0;
|
|
3352
|
-
const isLoading = isClientMode ? resourceHook.list.isLoading : loadingProp ?? false;
|
|
3353
|
-
const hasError = isClientMode ? resourceHook.list.isError : errorProp ?? false;
|
|
3354
|
-
const page = useResourcePage({
|
|
3355
|
-
resource: resourceName,
|
|
3356
|
-
data: displayData,
|
|
3357
|
-
total: displayTotal,
|
|
3358
|
-
columns: columnsProp,
|
|
3359
|
-
statusFilter,
|
|
3360
|
-
searchFields,
|
|
3361
|
-
initialFilters: initialFilters ?? (urlSeed && (urlSeed.q || urlSeed.sort) ? [
|
|
3362
|
-
...urlSeed.q ? [{ field: "__search__", operator: "contains", value: urlSeed.q }] : [],
|
|
3363
|
-
...urlSeed.sort ? [{ field: "__sort__", operator: "eq", value: urlSortKey }] : []
|
|
3364
|
-
] : void 0),
|
|
3365
|
-
onFiltersChange,
|
|
3366
|
-
defaultView,
|
|
3367
|
-
// Each page remembers its own table/cards choice, under the same key as
|
|
3368
|
-
// its density and page size — not one key shared by every data page.
|
|
3369
|
-
viewKey: viewSettingsKey,
|
|
3370
|
-
detail,
|
|
3371
|
-
titleField: titleFieldProp,
|
|
3372
|
-
subtitleField: subtitleFieldProp,
|
|
3373
|
-
controlled: isServerControlled
|
|
3374
|
-
});
|
|
3375
3458
|
const {
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
const statusField = statusFilter ? typeof statusFilter === "string" ? statusFilter : statusFilter.field : void 0;
|
|
3408
|
-
const defaultFilters = resourceOptions?.defaultFilters;
|
|
3409
|
-
const pushedStatusRef = useRef("");
|
|
3410
|
-
useEffect(() => {
|
|
3411
|
-
if (!resourceServerPaging || !statusField) return;
|
|
3412
|
-
const key = JSON.stringify([page.statusValue, defaultFilters ?? null]);
|
|
3413
|
-
if (key === pushedStatusRef.current) return;
|
|
3414
|
-
pushedStatusRef.current = key;
|
|
3415
|
-
setResourceFilters([
|
|
3416
|
-
...defaultFilters ?? [],
|
|
3417
|
-
...page.statusValue.length > 0 ? [{ field: statusField, operator: "in", value: page.statusValue }] : []
|
|
3418
|
-
]);
|
|
3419
|
-
}, [
|
|
3420
|
-
resourceServerPaging,
|
|
3421
|
-
statusField,
|
|
3422
|
-
page.statusValue,
|
|
3423
|
-
defaultFilters,
|
|
3424
|
-
setResourceFilters
|
|
3425
|
-
]);
|
|
3426
|
-
const clientTruncated = isClientMode && !resourceServerPaging && !isLoading && !hasError && displayTotal > displayData.length;
|
|
3427
|
-
const warnedTruncatedRef = useRef(false);
|
|
3428
|
-
useEffect(() => {
|
|
3429
|
-
if (!clientTruncated || warnedTruncatedRef.current) return;
|
|
3430
|
-
warnedTruncatedRef.current = true;
|
|
3431
|
-
console.warn(
|
|
3432
|
-
`ResourcePage: "${resourceName}" has ${displayTotal} rows but only ${displayData.length} were fetched. Pass \`pagination="server"\` to page through all of them, or raise \`clientRowLimit\`.`
|
|
3433
|
-
);
|
|
3434
|
-
}, [clientTruncated, resourceName, displayTotal, displayData.length]);
|
|
3435
|
-
const actions = useMemo(() => {
|
|
3436
|
-
if (isClientMode) {
|
|
3437
|
-
return {
|
|
3438
|
-
update: resourceHook.update,
|
|
3439
|
-
remove: resourceHook.remove,
|
|
3440
|
-
refetch: () => resourceHook.list.refetch()
|
|
3441
|
-
};
|
|
3442
|
-
}
|
|
3443
|
-
return {
|
|
3444
|
-
update: async () => {
|
|
3445
|
-
throw new Error(
|
|
3446
|
-
"ResourceActions.update not available in server-data mode"
|
|
3447
|
-
);
|
|
3448
|
-
},
|
|
3449
|
-
remove: async () => {
|
|
3450
|
-
throw new Error(
|
|
3451
|
-
"ResourceActions.remove not available in server-data mode"
|
|
3452
|
-
);
|
|
3453
|
-
},
|
|
3454
|
-
refetch: () => {
|
|
3455
|
-
}
|
|
3456
|
-
};
|
|
3457
|
-
}, [
|
|
3458
|
-
isClientMode,
|
|
3459
|
-
resourceHook.update,
|
|
3460
|
-
resourceHook.remove,
|
|
3461
|
-
resourceHook.list
|
|
3462
|
-
]);
|
|
3463
|
-
const keyOf = useCallback(
|
|
3464
|
-
(item) => getKeyProp ? getKeyProp(item) : String(item.id ?? ""),
|
|
3465
|
-
[getKeyProp]
|
|
3466
|
-
);
|
|
3467
|
-
const snapshotKey = page.selectedItem ? keyOf(page.selectedItem) : null;
|
|
3468
|
-
const selectedKey = urlPeek ? urlPeek.detailId : snapshotKey;
|
|
3469
|
-
const snapshot = page.selectedItem && snapshotKey === selectedKey ? page.selectedItem : null;
|
|
3470
|
-
const rowOrSnapshot = !selectedKey ? snapshot : displayData.find((row) => keyOf(row) === selectedKey) ?? snapshot;
|
|
3471
|
-
const linkedId = urlPeek && selectedKey && !rowOrSnapshot ? selectedKey : null;
|
|
3472
|
-
const linked = useOne(resourceName ?? "__noop__", linkedId, {
|
|
3473
|
-
enabled: isClientMode && !!linkedId
|
|
3474
|
-
});
|
|
3475
|
-
const selectedItem = rowOrSnapshot ?? (linkedId && linked.data ? linked.data : null);
|
|
3476
|
-
const linkMissing = !!linkedId && !selectedItem && !isLoading && (isClientMode ? linked.isError : true);
|
|
3477
|
-
const detailOpen = urlPeek ? selectedItem !== null || linkMissing : page.detailOpen;
|
|
3478
|
-
const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
3479
|
-
const handleToggleSelect = useCallback((id) => {
|
|
3480
|
-
setSelectedIds((prev) => {
|
|
3481
|
-
const next = new Set(prev);
|
|
3482
|
-
if (next.has(id)) next.delete(id);
|
|
3483
|
-
else next.add(id);
|
|
3484
|
-
return next;
|
|
3485
|
-
});
|
|
3486
|
-
}, []);
|
|
3487
|
-
const handleToggleAll = useCallback(() => {
|
|
3488
|
-
setSelectedIds((prev) => {
|
|
3489
|
-
if (prev.size === page.data.length) return /* @__PURE__ */ new Set();
|
|
3490
|
-
return new Set(page.data.map(keyOf));
|
|
3491
|
-
});
|
|
3492
|
-
}, [page.data, keyOf]);
|
|
3493
|
-
const handleClearSelection = useCallback(() => {
|
|
3494
|
-
setSelectedIds(/* @__PURE__ */ new Set());
|
|
3495
|
-
}, []);
|
|
3496
|
-
const [formOpen, setFormOpen] = useState(false);
|
|
3497
|
-
const [formAction, setFormAction] = useState("create");
|
|
3498
|
-
const [formItem, setFormItem] = useState(null);
|
|
3499
|
-
const useBuiltInCreate = allowCreate && !onCreate && isClientMode;
|
|
3500
|
-
const useBuiltInEdit = allowEdit && !onEdit && isClientMode;
|
|
3501
|
-
const handleCreate = useCallback(() => {
|
|
3502
|
-
if (useBuiltInCreate) {
|
|
3503
|
-
setFormAction("create");
|
|
3504
|
-
setFormItem(null);
|
|
3505
|
-
setFormOpen(true);
|
|
3506
|
-
} else if (onCreate) {
|
|
3507
|
-
onCreate();
|
|
3508
|
-
}
|
|
3509
|
-
}, [useBuiltInCreate, onCreate]);
|
|
3510
|
-
const allows = useCallback(
|
|
3511
|
-
(action, item) => {
|
|
3512
|
-
if (can && !can(action, item)) return false;
|
|
3513
|
-
if (!item) return true;
|
|
3514
|
-
if (canEditItem && !canEditItem(item)) return false;
|
|
3515
|
-
if (action === "edit" && canEdit && !canEdit(item)) return false;
|
|
3516
|
-
if (action === "delete" && canDelete && !canDelete(item)) return false;
|
|
3517
|
-
return true;
|
|
3518
|
-
},
|
|
3519
|
-
[can, canEditItem, canEdit, canDelete]
|
|
3520
|
-
);
|
|
3521
|
-
const handleEdit = useCallback(
|
|
3522
|
-
(item) => {
|
|
3523
|
-
if (!allows("edit", item)) return;
|
|
3524
|
-
if (useBuiltInEdit) {
|
|
3525
|
-
setFormAction("edit");
|
|
3526
|
-
setFormItem(item);
|
|
3527
|
-
setFormOpen(true);
|
|
3528
|
-
} else if (onEdit) {
|
|
3529
|
-
onEdit(item);
|
|
3530
|
-
}
|
|
3531
|
-
},
|
|
3532
|
-
[useBuiltInEdit, onEdit, allows]
|
|
3533
|
-
);
|
|
3534
|
-
const closeDetail = useCallback(() => {
|
|
3535
|
-
page.setDetailOpen(false);
|
|
3536
|
-
page.setSelectedItem(null);
|
|
3537
|
-
urlPeek?.setDetailId(null);
|
|
3538
|
-
}, [page, urlPeek]);
|
|
3539
|
-
const useBuiltInDelete = !!allowDelete && !onDelete && isClientMode;
|
|
3540
|
-
const deleteConfirmed = confirmDelete === void 0 ? useBuiltInDelete : !!confirmDelete;
|
|
3541
|
-
const deleteCopy = typeof confirmDelete === "object" ? confirmDelete : void 0;
|
|
3542
|
-
const [pendingDelete, setPendingDelete] = useState(null);
|
|
3543
|
-
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
3544
|
-
const { remove: removeRecord } = resourceHook;
|
|
3545
|
-
const { data: providerCaps } = useCapabilities(resourceName, {
|
|
3546
|
-
enabled: useBuiltInDelete && deleteConfirmed
|
|
3547
|
-
});
|
|
3548
|
-
const deleteKind = providerCaps?.softDelete ? "recycle" : "permanent";
|
|
3549
|
-
const performDelete = useCallback(
|
|
3550
|
-
async (item) => {
|
|
3551
|
-
if (useBuiltInDelete) {
|
|
3552
|
-
await removeRecord(
|
|
3553
|
-
getKeyProp ? keyOf(item) : item.id
|
|
3554
|
-
);
|
|
3555
|
-
} else if (onDelete) {
|
|
3556
|
-
await onDelete(item);
|
|
3557
|
-
}
|
|
3558
|
-
closeDetail();
|
|
3559
|
-
onDeleted?.(item);
|
|
3560
|
-
},
|
|
3561
|
-
[
|
|
3562
|
-
useBuiltInDelete,
|
|
3563
|
-
removeRecord,
|
|
3564
|
-
getKeyProp,
|
|
3565
|
-
keyOf,
|
|
3566
|
-
onDelete,
|
|
3567
|
-
closeDetail,
|
|
3568
|
-
onDeleted
|
|
3569
|
-
]
|
|
3570
|
-
);
|
|
3571
|
-
const deleteHandler = !(useBuiltInDelete || onDelete) ? void 0 : deleteConfirmed ? (item) => {
|
|
3572
|
-
setPendingDelete(item);
|
|
3573
|
-
setDeleteOpen(true);
|
|
3574
|
-
} : useBuiltInDelete ? (item) => {
|
|
3575
|
-
performDelete(item).catch((err) => {
|
|
3576
|
-
if (!resourceOptions?.onError) {
|
|
3577
|
-
toast.error(err instanceof Error ? err.message : "The delete failed.");
|
|
3578
|
-
}
|
|
3579
|
-
});
|
|
3580
|
-
} : (
|
|
3581
|
-
// Unconfirmed `onDelete` keeps its original contract exactly:
|
|
3582
|
-
// called straight from the peek, nothing closed for it.
|
|
3583
|
-
(item) => {
|
|
3584
|
-
const report = (err) => {
|
|
3585
|
-
if (resourceOptions?.onError) {
|
|
3586
|
-
resourceOptions.onError(err instanceof Error ? err : new Error(String(err)));
|
|
3587
|
-
} else {
|
|
3588
|
-
toast.error(err instanceof Error ? err.message : "The delete failed.");
|
|
3589
|
-
}
|
|
3590
|
-
};
|
|
3591
|
-
try {
|
|
3592
|
-
Promise.resolve(onDelete?.(item)).catch(report);
|
|
3593
|
-
} catch (err) {
|
|
3594
|
-
report(err);
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
);
|
|
3598
|
-
const selectedEditable = !!selectedItem && allows("edit", selectedItem);
|
|
3599
|
-
const selectedDeletable = !!selectedItem && allows("delete", selectedItem);
|
|
3600
|
-
const tableColumns = useMemo(() => {
|
|
3601
|
-
const base = isClientMode ? page.columns.map((col) => ({
|
|
3602
|
-
key: col.key,
|
|
3603
|
-
header: col.header ?? col.key,
|
|
3604
|
-
sortable: col.sortable ?? true,
|
|
3605
|
-
hideBelow: col.hideBelow,
|
|
3606
|
-
width: col.width,
|
|
3607
|
-
minWidth: col.minWidth,
|
|
3608
|
-
defaultVisible: col.defaultVisible,
|
|
3609
|
-
editable: col.editable,
|
|
3610
|
-
accessor: col.accessor,
|
|
3611
|
-
render: col.render ? (item, value) => col.render(item, value, actions) : void 0
|
|
3612
|
-
})) : page.tableColumns;
|
|
3613
|
-
if (!isServerControlled) return base;
|
|
3614
|
-
return base.map((col) => ({ ...col, sortable: false }));
|
|
3615
|
-
}, [
|
|
3459
|
+
actions,
|
|
3460
|
+
allows,
|
|
3461
|
+
clientTruncated,
|
|
3462
|
+
closeDetail,
|
|
3463
|
+
currentPage,
|
|
3464
|
+
deleteConfirmed,
|
|
3465
|
+
deleteCopy,
|
|
3466
|
+
deleteHandler,
|
|
3467
|
+
deleteKind,
|
|
3468
|
+
deleteOpen,
|
|
3469
|
+
detailOpen,
|
|
3470
|
+
detailSubtitle,
|
|
3471
|
+
detailTitle,
|
|
3472
|
+
displayData,
|
|
3473
|
+
displayTotal,
|
|
3474
|
+
effectivePageSize,
|
|
3475
|
+
formAction,
|
|
3476
|
+
formItem,
|
|
3477
|
+
formOpen,
|
|
3478
|
+
handleClearFilters,
|
|
3479
|
+
handleClearSelection,
|
|
3480
|
+
handleCreate,
|
|
3481
|
+
handleEdit,
|
|
3482
|
+
handlePageSizeChange,
|
|
3483
|
+
handleRowClick,
|
|
3484
|
+
handleSearchChange,
|
|
3485
|
+
handleSortSelect,
|
|
3486
|
+
handleTableSort,
|
|
3487
|
+
handleToggleAll,
|
|
3488
|
+
handleToggleSelect,
|
|
3489
|
+
hasError,
|
|
3616
3490
|
isClientMode,
|
|
3491
|
+
isLoading,
|
|
3617
3492
|
isServerControlled,
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
const pageSort = page.sort;
|
|
3624
|
-
const setPageSort = page.setSort;
|
|
3625
|
-
const tableSort = useMemo(() => {
|
|
3626
|
-
if (!pageSort) return [];
|
|
3627
|
-
const primary = { key: pageSort.field, dir: pageSort.order };
|
|
3628
|
-
const rest = tableTieBreaks && tableTieBreaks.primary === `${primary.key}:${primary.dir}` ? tableTieBreaks.rest : [];
|
|
3629
|
-
return [primary, ...rest];
|
|
3630
|
-
}, [pageSort, tableTieBreaks]);
|
|
3631
|
-
const handleTableSort = useCallback(
|
|
3632
|
-
(next) => {
|
|
3633
|
-
const [primary, ...rest] = next;
|
|
3634
|
-
setTableTieBreaks(primary ? { primary: `${primary.key}:${primary.dir}`, rest } : null);
|
|
3635
|
-
setPageSort(primary ? { field: primary.key, order: primary.dir } : null);
|
|
3636
|
-
},
|
|
3637
|
-
[setPageSort]
|
|
3638
|
-
);
|
|
3639
|
-
const pagerPage = resourceServerPaging ? resourceHook.params.pagination?.page ?? 1 : pageProp;
|
|
3640
|
-
const pagerPerPage = resourceServerPaging ? resourceHook.params.pagination?.perPage : perPageProp;
|
|
3641
|
-
const pagerTotal = resourceServerPaging ? displayTotal : totalProp;
|
|
3642
|
-
const pagerOnPageChange = resourceServerPaging ? resourceHook.setPage : onPageChange;
|
|
3643
|
-
const currentPage = pagerPage ?? 1;
|
|
3644
|
-
const writeUrl = url?.write;
|
|
3645
|
-
useEffect(() => {
|
|
3646
|
-
writeUrl?.({
|
|
3647
|
-
q: page.search,
|
|
3648
|
-
sort: page.sort,
|
|
3649
|
-
...resourceServerPaging ? { page: currentPage } : {}
|
|
3650
|
-
});
|
|
3651
|
-
}, [writeUrl, page.search, page.sort, currentPage, resourceServerPaging]);
|
|
3652
|
-
const handleSearchChange = useCallback(
|
|
3653
|
-
(value) => {
|
|
3654
|
-
page.filterConfig.onSearchChange(value);
|
|
3655
|
-
if (!isServerControlled) return;
|
|
3656
|
-
onSearchChange?.(value);
|
|
3657
|
-
if (currentPage !== 1 && !resourceServerPaging) onPageChange?.(1);
|
|
3658
|
-
},
|
|
3659
|
-
[
|
|
3660
|
-
page.filterConfig,
|
|
3661
|
-
isServerControlled,
|
|
3662
|
-
onSearchChange,
|
|
3663
|
-
onPageChange,
|
|
3664
|
-
resourceServerPaging,
|
|
3665
|
-
currentPage
|
|
3666
|
-
]
|
|
3667
|
-
);
|
|
3668
|
-
const handleSortSelect = useCallback(
|
|
3669
|
-
(value) => {
|
|
3670
|
-
page.filterConfig.onSortChange(value);
|
|
3671
|
-
if (!isServerControlled) return;
|
|
3672
|
-
const [field, order] = value.split(":");
|
|
3673
|
-
onSortChange?.(
|
|
3674
|
-
field && (order === "asc" || order === "desc") ? { field, order } : null
|
|
3675
|
-
);
|
|
3676
|
-
if (!resourceServerPaging) onPageChange?.(1);
|
|
3677
|
-
},
|
|
3678
|
-
[
|
|
3679
|
-
page.filterConfig,
|
|
3680
|
-
isServerControlled,
|
|
3681
|
-
onSortChange,
|
|
3682
|
-
onPageChange,
|
|
3683
|
-
resourceServerPaging
|
|
3684
|
-
]
|
|
3685
|
-
);
|
|
3686
|
-
const handleClearFilters = useCallback(() => {
|
|
3687
|
-
page.clearFilters();
|
|
3688
|
-
if (!isServerControlled) return;
|
|
3689
|
-
onSearchChange?.("");
|
|
3690
|
-
onSortChange?.(null);
|
|
3691
|
-
if (!resourceServerPaging) onPageChange?.(1);
|
|
3692
|
-
}, [
|
|
3493
|
+
keyOf,
|
|
3494
|
+
keyboardNav,
|
|
3495
|
+
linkMissing,
|
|
3496
|
+
linked,
|
|
3497
|
+
missingPagerProps,
|
|
3693
3498
|
page,
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
(size) => {
|
|
3721
|
-
updateSetting("pageSize", size);
|
|
3722
|
-
onPerPageChange?.(size);
|
|
3723
|
-
if (resourceServerPaging) resourceHook.setPerPage(size);
|
|
3724
|
-
else if (isServerControlled) onPageChange?.(1);
|
|
3725
|
-
},
|
|
3726
|
-
[
|
|
3727
|
-
updateSetting,
|
|
3728
|
-
onPerPageChange,
|
|
3729
|
-
resourceServerPaging,
|
|
3730
|
-
resourceHook.setPerPage,
|
|
3731
|
-
isServerControlled,
|
|
3732
|
-
onPageChange
|
|
3733
|
-
]
|
|
3734
|
-
);
|
|
3735
|
-
const missingPerPage = isServerControlled && !!pagerOnPageChange && pagerPerPage === void 0;
|
|
3736
|
-
const missingTotal = isServerControlled && !!pagerOnPageChange && pagerTotal === void 0;
|
|
3737
|
-
const pagerMisconfigured = missingPerPage || missingTotal;
|
|
3738
|
-
const missingPagerProps = [
|
|
3739
|
-
...missingPerPage ? ["perPage"] : [],
|
|
3740
|
-
...missingTotal ? ["total"] : []
|
|
3741
|
-
];
|
|
3742
|
-
const loggedMissingPerPageRef = useRef(false);
|
|
3743
|
-
useEffect(() => {
|
|
3744
|
-
if (!pagerMisconfigured) return;
|
|
3745
|
-
if (loggedMissingPerPageRef.current) return;
|
|
3746
|
-
loggedMissingPerPageRef.current = true;
|
|
3747
|
-
console.error(
|
|
3748
|
-
`ResourcePage: ${missingPagerProps.map((name) => `\`${name}\``).join(" and ")} ${missingPagerProps.length > 1 ? "are" : "is"} required when \`onPageChange\` is supplied.`
|
|
3749
|
-
);
|
|
3750
|
-
}, [pagerMisconfigured]);
|
|
3751
|
-
const pagerPageSize = pagerPerPage ?? effectivePageSize;
|
|
3752
|
-
const totalPages = Math.max(
|
|
3753
|
-
1,
|
|
3754
|
-
Math.ceil(displayTotal / Math.max(1, pagerPageSize))
|
|
3755
|
-
);
|
|
3756
|
-
const rangeFrom = page.data.length === 0 ? 0 : (currentPage - 1) * pagerPageSize + 1;
|
|
3757
|
-
const rangeTo = rangeFrom === 0 ? 0 : rangeFrom + page.data.length - 1;
|
|
3758
|
-
const { setPage: setResourcePage } = resourceHook;
|
|
3759
|
-
const settledPage = !isLoading && !hasError && !resourceHook.list.isPlaceholderData;
|
|
3760
|
-
useEffect(() => {
|
|
3761
|
-
if (!resourceServerPaging || !settledPage) return;
|
|
3762
|
-
if (currentPage > totalPages) setResourcePage(totalPages);
|
|
3763
|
-
}, [resourceServerPaging, settledPage, currentPage, totalPages, setResourcePage]);
|
|
3764
|
-
const titleOf = (item) => getTitle ? getTitle(item) : String(item[page.titleField] ?? "");
|
|
3765
|
-
const detailTitle = selectedItem ? titleOf(selectedItem) : "";
|
|
3766
|
-
const detailSubtitle = !selectedItem ? void 0 : getSubtitle ? getSubtitle(selectedItem) : page.subtitleField ? String(
|
|
3767
|
-
selectedItem[page.subtitleField] ?? ""
|
|
3768
|
-
) : void 0;
|
|
3769
|
-
const handleRowClick = (item) => {
|
|
3770
|
-
if (detail === "route") {
|
|
3771
|
-
const id = item.id;
|
|
3772
|
-
if (resourceName && id) {
|
|
3773
|
-
onNavigate(`/${resourceName}/${id}`);
|
|
3774
|
-
}
|
|
3775
|
-
return;
|
|
3776
|
-
}
|
|
3777
|
-
page.handleRowClick(item);
|
|
3778
|
-
urlPeek?.setDetailId(keyOf(item));
|
|
3779
|
-
};
|
|
3780
|
-
const hasBulkSelect = !!bulkActions && bulkActions.length > 0;
|
|
3781
|
-
const overlayOpen = detailOpen || formOpen || deleteOpen;
|
|
3782
|
-
const keyboardNav = useKeyboardNavigation({
|
|
3783
|
-
itemCount: page.data.length,
|
|
3784
|
-
enabled: keyboardNavigation && page.viewMode !== "table",
|
|
3785
|
-
overlayOpen,
|
|
3786
|
-
onOpen: (index) => {
|
|
3787
|
-
const item = page.data[index];
|
|
3788
|
-
if (item) handleRowClick(item);
|
|
3789
|
-
},
|
|
3790
|
-
onToggleSelect: hasBulkSelect ? (index) => {
|
|
3791
|
-
const item = page.data[index];
|
|
3792
|
-
if (item) {
|
|
3793
|
-
handleToggleSelect(keyOf(item));
|
|
3794
|
-
}
|
|
3795
|
-
} : void 0,
|
|
3796
|
-
// Supplied only while there is an open detail to close — the hook
|
|
3797
|
-
// suppresses Backspace's default whenever this is present, so leaving it
|
|
3798
|
-
// wired up permanently would swallow the browser/app's Backspace on
|
|
3799
|
-
// every grid/list page (including detail="none" and detail="route",
|
|
3800
|
-
// which never open one). `detailOpen` is only ever true for the panel
|
|
3801
|
-
// and modal modes, so it is the precise precondition here.
|
|
3802
|
-
onBack: detailOpen ? closeDetail : void 0
|
|
3803
|
-
});
|
|
3499
|
+
pagerMisconfigured,
|
|
3500
|
+
pagerOnPageChange,
|
|
3501
|
+
pendingDelete,
|
|
3502
|
+
performDelete,
|
|
3503
|
+
rangeFrom,
|
|
3504
|
+
rangeTo,
|
|
3505
|
+
resourceHook,
|
|
3506
|
+
resourceServerPaging,
|
|
3507
|
+
selectedDeletable,
|
|
3508
|
+
selectedEditable,
|
|
3509
|
+
selectedIds,
|
|
3510
|
+
selectedItem,
|
|
3511
|
+
serverSearchFields,
|
|
3512
|
+
setDeleteOpen,
|
|
3513
|
+
setFormOpen,
|
|
3514
|
+
settings,
|
|
3515
|
+
statusChipsConfig,
|
|
3516
|
+
tableColumns,
|
|
3517
|
+
tableSort,
|
|
3518
|
+
titleOf,
|
|
3519
|
+
totalPages,
|
|
3520
|
+
updateSetting,
|
|
3521
|
+
useBuiltInCreate,
|
|
3522
|
+
useBuiltInDelete,
|
|
3523
|
+
useBuiltInEdit
|
|
3524
|
+
} = useResourceTableModel(props);
|
|
3804
3525
|
const showCreateButton = (onCreate || useBuiltInCreate) && allows("create");
|
|
3805
3526
|
const createButton = showCreateButton ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: handleCreate, children: [
|
|
3806
3527
|
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
|