@dynostack/react-grid 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -3
- package/dist/index.cjs +228 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -4
- package/dist/index.d.ts +41 -4
- package/dist/index.js +229 -68
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { RowData, ColumnDef, ColumnPinningState, VisibilityState, Column, Table, CellContext, FilterFn } from '@tanstack/react-table';
|
|
2
|
+
import { RowData, ColumnDef, SortingState, ColumnFiltersState, ColumnPinningState, VisibilityState, Column, Table, CellContext, FilterFn } from '@tanstack/react-table';
|
|
3
3
|
import { CSSProperties } from 'react';
|
|
4
4
|
|
|
5
5
|
type RowActionId = "view" | "edit" | "duplicate" | "delete" | string;
|
|
@@ -168,17 +168,54 @@ type DataTableLabels = {
|
|
|
168
168
|
total?: string;
|
|
169
169
|
noData?: string;
|
|
170
170
|
noResults?: string;
|
|
171
|
+
loadError?: string;
|
|
172
|
+
retry?: string;
|
|
171
173
|
refreshing?: string;
|
|
172
174
|
rowsPerPage?: string;
|
|
173
175
|
page?: string;
|
|
174
176
|
of?: string;
|
|
175
177
|
};
|
|
176
178
|
type DataTableDensity = "compact" | "default" | "comfortable";
|
|
179
|
+
type DataTableFetchParams = {
|
|
180
|
+
pageIndex: number;
|
|
181
|
+
pageSize: number;
|
|
182
|
+
sorting: SortingState;
|
|
183
|
+
columnFilters: ColumnFiltersState;
|
|
184
|
+
globalFilter: string;
|
|
185
|
+
};
|
|
186
|
+
type DataTableDataSourceResult<TData> = TData[] | {
|
|
187
|
+
rows: TData[];
|
|
188
|
+
totalRecords?: number;
|
|
189
|
+
};
|
|
190
|
+
type DataTableErrorContext = {
|
|
191
|
+
type: "load" | "refresh";
|
|
192
|
+
message: string;
|
|
193
|
+
};
|
|
194
|
+
type DataTableDataSource<TData> = {
|
|
195
|
+
/**
|
|
196
|
+
* Fetch rows internally. In "client" mode, return the full data set and the
|
|
197
|
+
* table applies client-side sort/filter/pagination. In "server" mode, return
|
|
198
|
+
* the current page and optionally `totalRecords`.
|
|
199
|
+
*/
|
|
200
|
+
fetchRows: (params: DataTableFetchParams) => Promise<DataTableDataSourceResult<TData>>;
|
|
201
|
+
/** "client" by default. Use "server" for API-side pagination/filter/sort. */
|
|
202
|
+
mode?: "client" | "server";
|
|
203
|
+
/** Set false to pause fetching. */
|
|
204
|
+
enabled?: boolean;
|
|
205
|
+
/** Optional rows to show before the first fetch resolves. */
|
|
206
|
+
initialData?: TData[];
|
|
207
|
+
/** Refetch when these external values change. Memoize this array. */
|
|
208
|
+
deps?: readonly unknown[];
|
|
209
|
+
/** Called for load/refresh errors so apps can show a toast. */
|
|
210
|
+
onError?: (error: unknown, context: DataTableErrorContext) => void;
|
|
211
|
+
};
|
|
177
212
|
type DataTableProps<TData extends {
|
|
178
213
|
id: string | number;
|
|
179
214
|
}> = {
|
|
180
|
-
data
|
|
215
|
+
data?: TData[];
|
|
181
216
|
columns: ColumnDef<TData>[];
|
|
217
|
+
/** Optional internal fetcher. Omit this when using controlled `data`. */
|
|
218
|
+
dataSource?: DataTableDataSource<TData>;
|
|
182
219
|
isLoading?: boolean;
|
|
183
220
|
isFetching?: boolean;
|
|
184
221
|
onRefresh?: () => void;
|
|
@@ -221,7 +258,7 @@ type DataTableProps<TData extends {
|
|
|
221
258
|
};
|
|
222
259
|
declare function DataTable<TData extends {
|
|
223
260
|
id: string | number;
|
|
224
|
-
}>({ data, columns, isLoading, isFetching, onRefresh, totalRecords, exportFileName, enableSelection, renderSubRow, getSubRows, rowActions, customRowActions, onRowAction, onCellEdit, onRowSave, onAddRow, onBulkDelete, initialPageSize, pageSizeOptions, initialColumnPinning, initialColumnVisibility, globalFilter: globalFilterProp, onGlobalFilterChange, className, toolbarSlot, features, labels: labelsProp, density, theme, }: DataTableProps<TData>): react_jsx_runtime.JSX.Element;
|
|
261
|
+
}>({ data, columns, dataSource, isLoading, isFetching, onRefresh, totalRecords, exportFileName, enableSelection, renderSubRow, getSubRows, rowActions, customRowActions, onRowAction, onCellEdit, onRowSave, onAddRow, onBulkDelete, initialPageSize, pageSizeOptions, initialColumnPinning, initialColumnVisibility, globalFilter: globalFilterProp, onGlobalFilterChange, className, toolbarSlot, features, labels: labelsProp, density, theme, }: DataTableProps<TData>): react_jsx_runtime.JSX.Element;
|
|
225
262
|
|
|
226
263
|
type Props$2<TData, TValue> = {
|
|
227
264
|
column: Column<TData, TValue>;
|
|
@@ -313,4 +350,4 @@ declare function ColumnFilterPanel<TData, TValue>(props: CommonProps<TData, TVal
|
|
|
313
350
|
declare function exportToCsv<TData>(table: Table<TData>, fileName?: string): void;
|
|
314
351
|
declare function exportToExcel<TData>(table: Table<TData>, fileName?: string): void;
|
|
315
352
|
|
|
316
|
-
export { type AdvFilter, type CellEditorType, ColumnFilterPanel, type Combine, type Condition, type CustomRowAction, DataTable, type DataTableColumn, DataTableColumnHeader, type DataTableDensity, type DataTableFeatures, type DataTableLabels, DataTablePagination, type DataTableProps, DataTableRowActions, type DataTableRowActionsProps, type DataTableTheme, type DataTableThemeName, DataTableToolbar, type DateOp, EditableCell, type FilterType, type NumberOp, type RowAction, type RowActionId, type SelectOption, type SetFilter, type TextOp, booleanFilterFn, dateFilterFn, exportToCsv, exportToExcel, isFilterActive, numberFilterFn, setFilterFn, textFilterFn, themePresets, themeToStyle };
|
|
353
|
+
export { type AdvFilter, type CellEditorType, ColumnFilterPanel, type Combine, type Condition, type CustomRowAction, DataTable, type DataTableColumn, DataTableColumnHeader, type DataTableDataSource, type DataTableDataSourceResult, type DataTableDensity, type DataTableErrorContext, type DataTableFeatures, type DataTableFetchParams, type DataTableLabels, DataTablePagination, type DataTableProps, DataTableRowActions, type DataTableRowActionsProps, type DataTableTheme, type DataTableThemeName, DataTableToolbar, type DateOp, EditableCell, type FilterType, type NumberOp, type RowAction, type RowActionId, type SelectOption, type SetFilter, type TextOp, booleanFilterFn, dateFilterFn, exportToCsv, exportToExcel, isFilterActive, numberFilterFn, setFilterFn, textFilterFn, themePresets, themeToStyle };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { RowData, ColumnDef, ColumnPinningState, VisibilityState, Column, Table, CellContext, FilterFn } from '@tanstack/react-table';
|
|
2
|
+
import { RowData, ColumnDef, SortingState, ColumnFiltersState, ColumnPinningState, VisibilityState, Column, Table, CellContext, FilterFn } from '@tanstack/react-table';
|
|
3
3
|
import { CSSProperties } from 'react';
|
|
4
4
|
|
|
5
5
|
type RowActionId = "view" | "edit" | "duplicate" | "delete" | string;
|
|
@@ -168,17 +168,54 @@ type DataTableLabels = {
|
|
|
168
168
|
total?: string;
|
|
169
169
|
noData?: string;
|
|
170
170
|
noResults?: string;
|
|
171
|
+
loadError?: string;
|
|
172
|
+
retry?: string;
|
|
171
173
|
refreshing?: string;
|
|
172
174
|
rowsPerPage?: string;
|
|
173
175
|
page?: string;
|
|
174
176
|
of?: string;
|
|
175
177
|
};
|
|
176
178
|
type DataTableDensity = "compact" | "default" | "comfortable";
|
|
179
|
+
type DataTableFetchParams = {
|
|
180
|
+
pageIndex: number;
|
|
181
|
+
pageSize: number;
|
|
182
|
+
sorting: SortingState;
|
|
183
|
+
columnFilters: ColumnFiltersState;
|
|
184
|
+
globalFilter: string;
|
|
185
|
+
};
|
|
186
|
+
type DataTableDataSourceResult<TData> = TData[] | {
|
|
187
|
+
rows: TData[];
|
|
188
|
+
totalRecords?: number;
|
|
189
|
+
};
|
|
190
|
+
type DataTableErrorContext = {
|
|
191
|
+
type: "load" | "refresh";
|
|
192
|
+
message: string;
|
|
193
|
+
};
|
|
194
|
+
type DataTableDataSource<TData> = {
|
|
195
|
+
/**
|
|
196
|
+
* Fetch rows internally. In "client" mode, return the full data set and the
|
|
197
|
+
* table applies client-side sort/filter/pagination. In "server" mode, return
|
|
198
|
+
* the current page and optionally `totalRecords`.
|
|
199
|
+
*/
|
|
200
|
+
fetchRows: (params: DataTableFetchParams) => Promise<DataTableDataSourceResult<TData>>;
|
|
201
|
+
/** "client" by default. Use "server" for API-side pagination/filter/sort. */
|
|
202
|
+
mode?: "client" | "server";
|
|
203
|
+
/** Set false to pause fetching. */
|
|
204
|
+
enabled?: boolean;
|
|
205
|
+
/** Optional rows to show before the first fetch resolves. */
|
|
206
|
+
initialData?: TData[];
|
|
207
|
+
/** Refetch when these external values change. Memoize this array. */
|
|
208
|
+
deps?: readonly unknown[];
|
|
209
|
+
/** Called for load/refresh errors so apps can show a toast. */
|
|
210
|
+
onError?: (error: unknown, context: DataTableErrorContext) => void;
|
|
211
|
+
};
|
|
177
212
|
type DataTableProps<TData extends {
|
|
178
213
|
id: string | number;
|
|
179
214
|
}> = {
|
|
180
|
-
data
|
|
215
|
+
data?: TData[];
|
|
181
216
|
columns: ColumnDef<TData>[];
|
|
217
|
+
/** Optional internal fetcher. Omit this when using controlled `data`. */
|
|
218
|
+
dataSource?: DataTableDataSource<TData>;
|
|
182
219
|
isLoading?: boolean;
|
|
183
220
|
isFetching?: boolean;
|
|
184
221
|
onRefresh?: () => void;
|
|
@@ -221,7 +258,7 @@ type DataTableProps<TData extends {
|
|
|
221
258
|
};
|
|
222
259
|
declare function DataTable<TData extends {
|
|
223
260
|
id: string | number;
|
|
224
|
-
}>({ data, columns, isLoading, isFetching, onRefresh, totalRecords, exportFileName, enableSelection, renderSubRow, getSubRows, rowActions, customRowActions, onRowAction, onCellEdit, onRowSave, onAddRow, onBulkDelete, initialPageSize, pageSizeOptions, initialColumnPinning, initialColumnVisibility, globalFilter: globalFilterProp, onGlobalFilterChange, className, toolbarSlot, features, labels: labelsProp, density, theme, }: DataTableProps<TData>): react_jsx_runtime.JSX.Element;
|
|
261
|
+
}>({ data, columns, dataSource, isLoading, isFetching, onRefresh, totalRecords, exportFileName, enableSelection, renderSubRow, getSubRows, rowActions, customRowActions, onRowAction, onCellEdit, onRowSave, onAddRow, onBulkDelete, initialPageSize, pageSizeOptions, initialColumnPinning, initialColumnVisibility, globalFilter: globalFilterProp, onGlobalFilterChange, className, toolbarSlot, features, labels: labelsProp, density, theme, }: DataTableProps<TData>): react_jsx_runtime.JSX.Element;
|
|
225
262
|
|
|
226
263
|
type Props$2<TData, TValue> = {
|
|
227
264
|
column: Column<TData, TValue>;
|
|
@@ -313,4 +350,4 @@ declare function ColumnFilterPanel<TData, TValue>(props: CommonProps<TData, TVal
|
|
|
313
350
|
declare function exportToCsv<TData>(table: Table<TData>, fileName?: string): void;
|
|
314
351
|
declare function exportToExcel<TData>(table: Table<TData>, fileName?: string): void;
|
|
315
352
|
|
|
316
|
-
export { type AdvFilter, type CellEditorType, ColumnFilterPanel, type Combine, type Condition, type CustomRowAction, DataTable, type DataTableColumn, DataTableColumnHeader, type DataTableDensity, type DataTableFeatures, type DataTableLabels, DataTablePagination, type DataTableProps, DataTableRowActions, type DataTableRowActionsProps, type DataTableTheme, type DataTableThemeName, DataTableToolbar, type DateOp, EditableCell, type FilterType, type NumberOp, type RowAction, type RowActionId, type SelectOption, type SetFilter, type TextOp, booleanFilterFn, dateFilterFn, exportToCsv, exportToExcel, isFilterActive, numberFilterFn, setFilterFn, textFilterFn, themePresets, themeToStyle };
|
|
353
|
+
export { type AdvFilter, type CellEditorType, ColumnFilterPanel, type Combine, type Condition, type CustomRowAction, DataTable, type DataTableColumn, DataTableColumnHeader, type DataTableDataSource, type DataTableDataSourceResult, type DataTableDensity, type DataTableErrorContext, type DataTableFeatures, type DataTableFetchParams, type DataTableLabels, DataTablePagination, type DataTableProps, DataTableRowActions, type DataTableRowActionsProps, type DataTableTheme, type DataTableThemeName, DataTableToolbar, type DateOp, EditableCell, type FilterType, type NumberOp, type RowAction, type RowActionId, type SelectOption, type SetFilter, type TextOp, booleanFilterFn, dateFilterFn, exportToCsv, exportToExcel, isFilterActive, numberFilterFn, setFilterFn, textFilterFn, themePresets, themeToStyle };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
|
|
1
|
+
import { createContext, useState, useRef, useEffect, useMemo, useLayoutEffect, useContext } from 'react';
|
|
2
2
|
import { useReactTable, getPaginationRowModel, getExpandedRowModel, getSortedRowModel, getFilteredRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
|
|
3
3
|
import { Trash2Icon, XIcon, SearchIcon, RefreshCwIcon, ColumnsIcon, DownloadIcon, FileTextIcon, FileSpreadsheetIcon, PlusIcon, ChevronsLeftIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsRightIcon, ArrowDownIcon, ArrowUpIcon, ChevronsUpDownIcon, PinOffIcon, PinIcon, EyeOffIcon, FilterIcon, CheckIcon, MoreHorizontalIcon, ChevronDownIcon, GripVerticalIcon, Loader2Icon, MinusIcon, CopyIcon, PencilIcon, EyeIcon, ChevronUpIcon } from 'lucide-react';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
@@ -6,6 +6,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
6
6
|
import { Slot, Separator as Separator$1, Popover as Popover$1, Checkbox as Checkbox$1, DropdownMenu as DropdownMenu$1, Select as Select$1 } from 'radix-ui';
|
|
7
7
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
8
8
|
import { cva } from 'class-variance-authority';
|
|
9
|
+
import { createPortal } from 'react-dom';
|
|
9
10
|
|
|
10
11
|
function cn(...inputs) {
|
|
11
12
|
return twMerge(clsx(inputs));
|
|
@@ -1715,14 +1716,34 @@ function DataTableRowActions({
|
|
|
1715
1716
|
onCancelEdit
|
|
1716
1717
|
}) {
|
|
1717
1718
|
const [open, setOpen] = useState(false);
|
|
1719
|
+
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
|
1720
|
+
const triggerRef = useRef(null);
|
|
1718
1721
|
const menuRef = useRef(null);
|
|
1722
|
+
useLayoutEffect(() => {
|
|
1723
|
+
if (!open || !triggerRef.current) return;
|
|
1724
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
1725
|
+
setMenuPosition({
|
|
1726
|
+
top: rect.bottom + 4,
|
|
1727
|
+
left: Math.max(8, rect.right - 160)
|
|
1728
|
+
});
|
|
1729
|
+
}, [open]);
|
|
1719
1730
|
useEffect(() => {
|
|
1720
1731
|
if (!open) return;
|
|
1721
1732
|
const onPointerDown = (event) => {
|
|
1722
|
-
|
|
1733
|
+
const target = event.target;
|
|
1734
|
+
if (!menuRef.current?.contains(target) && !triggerRef.current?.contains(target)) {
|
|
1735
|
+
setOpen(false);
|
|
1736
|
+
}
|
|
1723
1737
|
};
|
|
1738
|
+
const onDismiss = () => setOpen(false);
|
|
1724
1739
|
document.addEventListener("pointerdown", onPointerDown);
|
|
1725
|
-
|
|
1740
|
+
window.addEventListener("scroll", onDismiss, true);
|
|
1741
|
+
window.addEventListener("resize", onDismiss);
|
|
1742
|
+
return () => {
|
|
1743
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
1744
|
+
window.removeEventListener("scroll", onDismiss, true);
|
|
1745
|
+
window.removeEventListener("resize", onDismiss);
|
|
1746
|
+
};
|
|
1726
1747
|
}, [open]);
|
|
1727
1748
|
if (isEditing) {
|
|
1728
1749
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1", children: [
|
|
@@ -1753,6 +1774,7 @@ function DataTableRowActions({
|
|
|
1753
1774
|
/* @__PURE__ */ jsx(
|
|
1754
1775
|
Button,
|
|
1755
1776
|
{
|
|
1777
|
+
ref: triggerRef,
|
|
1756
1778
|
variant: "ghost",
|
|
1757
1779
|
size: "icon-xs",
|
|
1758
1780
|
"aria-label": "Open row actions",
|
|
@@ -1761,41 +1783,52 @@ function DataTableRowActions({
|
|
|
1761
1783
|
children: /* @__PURE__ */ jsx(MoreHorizontalIcon, {})
|
|
1762
1784
|
}
|
|
1763
1785
|
),
|
|
1764
|
-
open &&
|
|
1765
|
-
|
|
1766
|
-
|
|
1786
|
+
open && createPortal(
|
|
1787
|
+
/* @__PURE__ */ jsxs(
|
|
1788
|
+
"div",
|
|
1767
1789
|
{
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
onAction?.(a, row);
|
|
1772
|
-
},
|
|
1790
|
+
ref: menuRef,
|
|
1791
|
+
className: "fixed z-[1000] w-40 rounded-md border bg-popover p-1 text-popover-foreground shadow-lg",
|
|
1792
|
+
style: { top: menuPosition.top, left: menuPosition.left },
|
|
1773
1793
|
children: [
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
c.
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
+
actions.map((a) => /* @__PURE__ */ jsxs(
|
|
1795
|
+
RowActionButton,
|
|
1796
|
+
{
|
|
1797
|
+
danger: a === "delete",
|
|
1798
|
+
onClick: () => {
|
|
1799
|
+
setOpen(false);
|
|
1800
|
+
onAction?.(a, row);
|
|
1801
|
+
},
|
|
1802
|
+
children: [
|
|
1803
|
+
builtinIcons[a],
|
|
1804
|
+
" ",
|
|
1805
|
+
builtinLabels[a]
|
|
1806
|
+
]
|
|
1807
|
+
},
|
|
1808
|
+
a
|
|
1809
|
+
)),
|
|
1810
|
+
customActions.length > 0 && actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "-mx-1 my-1 h-px bg-border" }),
|
|
1811
|
+
customActions.filter((c) => c.show ? c.show(row) : true).map((c) => /* @__PURE__ */ jsxs(
|
|
1812
|
+
RowActionButton,
|
|
1813
|
+
{
|
|
1814
|
+
danger: c.danger,
|
|
1815
|
+
onClick: () => {
|
|
1816
|
+
setOpen(false);
|
|
1817
|
+
onAction?.(c.id, row);
|
|
1818
|
+
},
|
|
1819
|
+
children: [
|
|
1820
|
+
c.icon,
|
|
1821
|
+
" ",
|
|
1822
|
+
c.label
|
|
1823
|
+
]
|
|
1824
|
+
},
|
|
1825
|
+
c.id
|
|
1826
|
+
))
|
|
1794
1827
|
]
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1828
|
+
}
|
|
1829
|
+
),
|
|
1830
|
+
document.body
|
|
1831
|
+
)
|
|
1799
1832
|
] });
|
|
1800
1833
|
}
|
|
1801
1834
|
function RowActionButton({
|
|
@@ -2164,6 +2197,8 @@ var DEFAULT_LABELS = {
|
|
|
2164
2197
|
total: "Total",
|
|
2165
2198
|
noData: "No data.",
|
|
2166
2199
|
noResults: "No rows match the current filters.",
|
|
2200
|
+
loadError: "Could not load rows.",
|
|
2201
|
+
retry: "Retry",
|
|
2167
2202
|
refreshing: "Refreshing",
|
|
2168
2203
|
rowsPerPage: "Rows per page",
|
|
2169
2204
|
page: "Page",
|
|
@@ -2177,6 +2212,7 @@ var DENSITY_CLASS = {
|
|
|
2177
2212
|
function DataTable({
|
|
2178
2213
|
data,
|
|
2179
2214
|
columns,
|
|
2215
|
+
dataSource,
|
|
2180
2216
|
isLoading,
|
|
2181
2217
|
isFetching,
|
|
2182
2218
|
onRefresh,
|
|
@@ -2208,7 +2244,7 @@ function DataTable({
|
|
|
2208
2244
|
const labels = { ...DEFAULT_LABELS, ...labelsProp };
|
|
2209
2245
|
const f = {
|
|
2210
2246
|
search: features?.search ?? true,
|
|
2211
|
-
refresh: features?.refresh ?? !!onRefresh,
|
|
2247
|
+
refresh: features?.refresh ?? !!(onRefresh || dataSource),
|
|
2212
2248
|
columnVisibility: features?.columnVisibility ?? true,
|
|
2213
2249
|
export: features?.export ?? true,
|
|
2214
2250
|
addRow: features?.addRow ?? !!onAddRow,
|
|
@@ -2252,14 +2288,80 @@ function DataTable({
|
|
|
2252
2288
|
pageIndex: 0,
|
|
2253
2289
|
pageSize: initialPageSize
|
|
2254
2290
|
});
|
|
2291
|
+
const [sourceRows, setSourceRows] = useState(
|
|
2292
|
+
() => dataSource?.initialData ?? []
|
|
2293
|
+
);
|
|
2294
|
+
const [sourceTotalRecords, setSourceTotalRecords] = useState(void 0);
|
|
2295
|
+
const [sourceError, setSourceError] = useState(null);
|
|
2296
|
+
const [sourceLoading, setSourceLoading] = useState(false);
|
|
2297
|
+
const [sourceFetching, setSourceFetching] = useState(false);
|
|
2298
|
+
const [sourceRefreshKey, setSourceRefreshKey] = useState(0);
|
|
2255
2299
|
const [editingCell, setEditingCell] = useState(null);
|
|
2256
2300
|
const [editingRowId, setEditingRowId] = useState(null);
|
|
2257
2301
|
const [rowDraft, setRowDraft] = useState({});
|
|
2302
|
+
const dataSourceEnabled = !!dataSource && dataSource.enabled !== false;
|
|
2303
|
+
const dataSourceMode = dataSource?.mode ?? "client";
|
|
2304
|
+
const isServerSideDataSource = dataSourceEnabled && dataSourceMode === "server";
|
|
2305
|
+
const dataSourceDeps = dataSource?.deps ?? [];
|
|
2306
|
+
useEffect(() => {
|
|
2307
|
+
if (!dataSourceEnabled || !dataSource) return;
|
|
2308
|
+
let cancelled = false;
|
|
2309
|
+
const isInitialLoad = sourceRows.length === 0;
|
|
2310
|
+
setSourceError(null);
|
|
2311
|
+
setSourceLoading(isInitialLoad);
|
|
2312
|
+
setSourceFetching(!isInitialLoad);
|
|
2313
|
+
dataSource.fetchRows({
|
|
2314
|
+
pageIndex: pagination.pageIndex,
|
|
2315
|
+
pageSize: pagination.pageSize,
|
|
2316
|
+
sorting,
|
|
2317
|
+
columnFilters,
|
|
2318
|
+
globalFilter
|
|
2319
|
+
}).then((result) => {
|
|
2320
|
+
if (cancelled) return;
|
|
2321
|
+
const rows2 = Array.isArray(result) ? result : result.rows;
|
|
2322
|
+
setSourceRows(rows2);
|
|
2323
|
+
setSourceTotalRecords(
|
|
2324
|
+
Array.isArray(result) ? rows2.length : result.totalRecords ?? rows2.length
|
|
2325
|
+
);
|
|
2326
|
+
}).catch((error) => {
|
|
2327
|
+
if (cancelled) return;
|
|
2328
|
+
setSourceError(error);
|
|
2329
|
+
dataSource.onError?.(error, {
|
|
2330
|
+
type: isInitialLoad ? "load" : "refresh",
|
|
2331
|
+
message: isInitialLoad ? labels.loadError : labels.refreshing
|
|
2332
|
+
});
|
|
2333
|
+
}).finally(() => {
|
|
2334
|
+
if (cancelled) return;
|
|
2335
|
+
setSourceLoading(false);
|
|
2336
|
+
setSourceFetching(false);
|
|
2337
|
+
});
|
|
2338
|
+
return () => {
|
|
2339
|
+
cancelled = true;
|
|
2340
|
+
};
|
|
2341
|
+
}, [
|
|
2342
|
+
dataSourceEnabled,
|
|
2343
|
+
dataSource,
|
|
2344
|
+
sourceRefreshKey,
|
|
2345
|
+
pagination.pageIndex,
|
|
2346
|
+
pagination.pageSize,
|
|
2347
|
+
sorting,
|
|
2348
|
+
columnFilters,
|
|
2349
|
+
globalFilter,
|
|
2350
|
+
labels.loadError,
|
|
2351
|
+
labels.refreshing,
|
|
2352
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2353
|
+
...dataSourceDeps
|
|
2354
|
+
]);
|
|
2355
|
+
const baseData = dataSourceEnabled ? sourceRows : data ?? [];
|
|
2356
|
+
const effectiveIsLoading = dataSourceEnabled ? sourceLoading : isLoading;
|
|
2357
|
+
const effectiveIsFetching = dataSourceEnabled ? sourceFetching : isFetching;
|
|
2358
|
+
const effectiveOnRefresh = dataSourceEnabled ? () => setSourceRefreshKey((key) => key + 1) : onRefresh;
|
|
2258
2359
|
const [extraRows, setExtraRows] = useState([]);
|
|
2259
2360
|
const mergedData = useMemo(
|
|
2260
|
-
() => extraRows.length ? [...extraRows, ...
|
|
2261
|
-
[extraRows,
|
|
2361
|
+
() => extraRows.length ? [...extraRows, ...baseData] : baseData,
|
|
2362
|
+
[extraRows, baseData]
|
|
2262
2363
|
);
|
|
2364
|
+
const effectiveTotalRecords = totalRecords ?? (dataSourceEnabled ? sourceTotalRecords ?? mergedData.length : mergedData.length);
|
|
2263
2365
|
const augmentedColumns = useMemo(() => {
|
|
2264
2366
|
const wrap = (col) => {
|
|
2265
2367
|
const meta = col.meta;
|
|
@@ -2409,6 +2511,10 @@ function DataTable({
|
|
|
2409
2511
|
enableRowSelection: true,
|
|
2410
2512
|
enableExpanding: !!(renderSubRow || getSubRows),
|
|
2411
2513
|
enablePinning: f.pinning,
|
|
2514
|
+
manualPagination: isServerSideDataSource,
|
|
2515
|
+
manualSorting: isServerSideDataSource,
|
|
2516
|
+
manualFiltering: isServerSideDataSource,
|
|
2517
|
+
pageCount: isServerSideDataSource ? Math.max(1, Math.ceil(effectiveTotalRecords / pagination.pageSize)) : void 0,
|
|
2412
2518
|
getRowId: (row) => String(row.id),
|
|
2413
2519
|
getSubRows,
|
|
2414
2520
|
getRowCanExpand: (row) => {
|
|
@@ -2433,27 +2539,56 @@ function DataTable({
|
|
|
2433
2539
|
getExpandedRowModel: getExpandedRowModel(),
|
|
2434
2540
|
getPaginationRowModel: getPaginationRowModel()
|
|
2435
2541
|
});
|
|
2436
|
-
const
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2542
|
+
const reorderState = useRef(null);
|
|
2543
|
+
const [activeReorderId, setActiveReorderId] = useState(null);
|
|
2544
|
+
const [dragOverColId, setDragOverColId] = useState(null);
|
|
2545
|
+
const isInteractiveTarget = (target) => {
|
|
2546
|
+
const el = target;
|
|
2547
|
+
if (!el || !el.closest) return false;
|
|
2548
|
+
return !!el.closest(
|
|
2549
|
+
'button, a, input, select, textarea, [role="menuitem"], [data-no-drag]'
|
|
2550
|
+
);
|
|
2441
2551
|
};
|
|
2442
|
-
const
|
|
2443
|
-
e.
|
|
2444
|
-
e.
|
|
2552
|
+
const onHeaderPointerDown = (id) => (e) => {
|
|
2553
|
+
if (e.button !== 0) return;
|
|
2554
|
+
if (isInteractiveTarget(e.target)) return;
|
|
2555
|
+
reorderState.current = { x: e.clientX, y: e.clientY, id };
|
|
2445
2556
|
};
|
|
2446
|
-
const
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
if (!
|
|
2557
|
+
const onHeaderPointerMove = (e) => {
|
|
2558
|
+
const start = reorderState.current;
|
|
2559
|
+
if (!start) return;
|
|
2560
|
+
if (!activeReorderId) {
|
|
2561
|
+
const dx = e.clientX - start.x;
|
|
2562
|
+
const dy = e.clientY - start.y;
|
|
2563
|
+
if (Math.hypot(dx, dy) < 6) return;
|
|
2564
|
+
setActiveReorderId(start.id);
|
|
2565
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
2566
|
+
}
|
|
2567
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
2568
|
+
const th = el ? el.closest("th[data-col-id]") : null;
|
|
2569
|
+
const overId = th?.dataset.colId ?? null;
|
|
2570
|
+
const next = overId && overId !== start.id ? overId : null;
|
|
2571
|
+
if (next !== dragOverColId) setDragOverColId(next);
|
|
2572
|
+
};
|
|
2573
|
+
const onHeaderPointerUp = (e) => {
|
|
2574
|
+
const start = reorderState.current;
|
|
2575
|
+
reorderState.current = null;
|
|
2576
|
+
if (!activeReorderId) return;
|
|
2577
|
+
try {
|
|
2578
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
2579
|
+
} catch {
|
|
2580
|
+
}
|
|
2581
|
+
const target = dragOverColId;
|
|
2582
|
+
setActiveReorderId(null);
|
|
2583
|
+
setDragOverColId(null);
|
|
2584
|
+
if (!start || !target || target === start.id) return;
|
|
2450
2585
|
setColumnOrder((order) => {
|
|
2451
2586
|
const next = [...order];
|
|
2452
|
-
const from = next.indexOf(
|
|
2453
|
-
const to = next.indexOf(
|
|
2587
|
+
const from = next.indexOf(start.id);
|
|
2588
|
+
const to = next.indexOf(target);
|
|
2454
2589
|
if (from === -1 || to === -1) return order;
|
|
2455
2590
|
next.splice(from, 1);
|
|
2456
|
-
next.splice(to, 0,
|
|
2591
|
+
next.splice(to, 0, start.id);
|
|
2457
2592
|
return next;
|
|
2458
2593
|
});
|
|
2459
2594
|
};
|
|
@@ -2510,9 +2645,9 @@ function DataTable({
|
|
|
2510
2645
|
DataTableToolbar,
|
|
2511
2646
|
{
|
|
2512
2647
|
table,
|
|
2513
|
-
totalRecords:
|
|
2514
|
-
isFetching,
|
|
2515
|
-
onRefresh: f.refresh ?
|
|
2648
|
+
totalRecords: effectiveTotalRecords,
|
|
2649
|
+
isFetching: effectiveIsFetching,
|
|
2650
|
+
onRefresh: f.refresh ? effectiveOnRefresh : void 0,
|
|
2516
2651
|
onAddRow: f.addRow ? handleAddRow : void 0,
|
|
2517
2652
|
onBulkDelete,
|
|
2518
2653
|
exportFileName,
|
|
@@ -2540,32 +2675,38 @@ function DataTable({
|
|
|
2540
2675
|
const meta = header.column.columnDef.meta;
|
|
2541
2676
|
const isUtility = header.column.id === "__select" || header.column.id === "__expand" || header.column.id === "__actions";
|
|
2542
2677
|
const isPinned = !!header.column.getIsPinned();
|
|
2678
|
+
const canReorder = !isUtility && f.reordering;
|
|
2679
|
+
const isDropTarget = canReorder && dragOverColId === header.column.id;
|
|
2680
|
+
const isBeingDragged = canReorder && activeReorderId === header.column.id;
|
|
2543
2681
|
return /* @__PURE__ */ jsxs(
|
|
2544
2682
|
"th",
|
|
2545
2683
|
{
|
|
2546
2684
|
colSpan: header.colSpan,
|
|
2685
|
+
"data-col-id": header.column.id,
|
|
2686
|
+
onPointerDown: canReorder ? onHeaderPointerDown(header.column.id) : void 0,
|
|
2687
|
+
onPointerMove: canReorder ? onHeaderPointerMove : void 0,
|
|
2688
|
+
onPointerUp: canReorder ? onHeaderPointerUp : void 0,
|
|
2689
|
+
onPointerCancel: canReorder ? onHeaderPointerUp : void 0,
|
|
2547
2690
|
style: {
|
|
2548
2691
|
width: header.getSize(),
|
|
2549
2692
|
...getPinningStyles(header.column),
|
|
2550
2693
|
...isPinned ? { zIndex: 2 } : {}
|
|
2551
2694
|
},
|
|
2552
2695
|
className: cn(
|
|
2553
|
-
"group/th relative h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground",
|
|
2696
|
+
"group/th relative h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground select-none",
|
|
2697
|
+
canReorder && "cursor-grab active:cursor-grabbing",
|
|
2554
2698
|
isPinned && "bg-card",
|
|
2699
|
+
isBeingDragged && "opacity-50",
|
|
2700
|
+
isDropTarget && "bg-primary/10 ring-2 ring-inset ring-primary/40",
|
|
2555
2701
|
meta?.headerClassName
|
|
2556
2702
|
),
|
|
2557
2703
|
children: [
|
|
2558
2704
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2559
|
-
|
|
2560
|
-
|
|
2705
|
+
canReorder && /* @__PURE__ */ jsx(
|
|
2706
|
+
GripVerticalIcon,
|
|
2561
2707
|
{
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
onDragOver: onHeaderDragOver,
|
|
2565
|
-
onDrop: () => onHeaderDrop(header.column.id),
|
|
2566
|
-
className: "-ml-1 cursor-grab rounded p-0.5 text-muted-foreground/40 opacity-0 transition group-hover/th:opacity-100 hover:bg-muted hover:text-foreground active:cursor-grabbing",
|
|
2567
|
-
title: "Drag to reorder",
|
|
2568
|
-
children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-3" })
|
|
2708
|
+
className: "-ml-1 size-3 shrink-0 text-muted-foreground/40 opacity-0 transition group-hover/th:opacity-100",
|
|
2709
|
+
"aria-hidden": "true"
|
|
2569
2710
|
}
|
|
2570
2711
|
),
|
|
2571
2712
|
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) })
|
|
@@ -2573,6 +2714,8 @@ function DataTable({
|
|
|
2573
2714
|
header.column.getCanResize() && /* @__PURE__ */ jsx(
|
|
2574
2715
|
"span",
|
|
2575
2716
|
{
|
|
2717
|
+
"data-no-drag": true,
|
|
2718
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
2576
2719
|
onMouseDown: header.getResizeHandler(),
|
|
2577
2720
|
onTouchStart: header.getResizeHandler(),
|
|
2578
2721
|
onDoubleClick: () => header.column.resetSize(),
|
|
@@ -2587,7 +2730,25 @@ function DataTable({
|
|
|
2587
2730
|
header.id
|
|
2588
2731
|
);
|
|
2589
2732
|
}) }, hg.id)) }),
|
|
2590
|
-
/* @__PURE__ */ jsx("tbody", { children:
|
|
2733
|
+
/* @__PURE__ */ jsx("tbody", { children: effectiveIsLoading ? renderSkeleton(visibleLeafCount, pagination.pageSize) : sourceError && rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
|
|
2734
|
+
"td",
|
|
2735
|
+
{
|
|
2736
|
+
colSpan: visibleLeafCount,
|
|
2737
|
+
className: "h-32 text-center text-sm text-muted-foreground",
|
|
2738
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
|
|
2739
|
+
/* @__PURE__ */ jsx("span", { children: labels.loadError }),
|
|
2740
|
+
effectiveOnRefresh && /* @__PURE__ */ jsx(
|
|
2741
|
+
"button",
|
|
2742
|
+
{
|
|
2743
|
+
type: "button",
|
|
2744
|
+
onClick: effectiveOnRefresh,
|
|
2745
|
+
className: "rounded-md border px-2 py-1 text-xs text-foreground hover:bg-muted",
|
|
2746
|
+
children: labels.retry
|
|
2747
|
+
}
|
|
2748
|
+
)
|
|
2749
|
+
] })
|
|
2750
|
+
}
|
|
2751
|
+
) }) : rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
|
|
2591
2752
|
"td",
|
|
2592
2753
|
{
|
|
2593
2754
|
colSpan: visibleLeafCount,
|
|
@@ -2606,7 +2767,7 @@ function DataTable({
|
|
|
2606
2767
|
]
|
|
2607
2768
|
}
|
|
2608
2769
|
),
|
|
2609
|
-
|
|
2770
|
+
effectiveIsFetching && !effectiveIsLoading && /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute top-1.5 right-2 flex items-center gap-1.5 rounded-md bg-background/80 px-2 py-1 text-xs text-muted-foreground shadow-sm backdrop-blur", children: [
|
|
2610
2771
|
/* @__PURE__ */ jsx(Loader2Icon, { className: "size-3 animate-spin" }),
|
|
2611
2772
|
" ",
|
|
2612
2773
|
labels.refreshing
|