@abaktiar/datagrid
Version:
A powerful, feature-rich React DataGrid component with TypeScript support, built on TanStack Table. Features customizable floating action dock, Excel/CSV export, and advanced row selection.
1,296 lines • 109 kB
JavaScript
import { useState, useRef, useCallback, useEffect, memo, useMemo, createContext, useContext } from "react";
import { flexRender, useReactTable, getPaginationRowModel, getFilteredRowModel, getSortedRowModel, getCoreRowModel } from "@tanstack/react-table";
import * as XLSX from "xlsx";
var jsxRuntime = { exports: {} };
var reactJsxRuntime_production = {};
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_production;
function requireReactJsxRuntime_production() {
if (hasRequiredReactJsxRuntime_production) return reactJsxRuntime_production;
hasRequiredReactJsxRuntime_production = 1;
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
function jsxProd(type, config, maybeKey) {
var key = null;
void 0 !== maybeKey && (key = "" + maybeKey);
void 0 !== config.key && (key = "" + config.key);
if ("key" in config) {
maybeKey = {};
for (var propName in config)
"key" !== propName && (maybeKey[propName] = config[propName]);
} else maybeKey = config;
config = maybeKey.ref;
return {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref: void 0 !== config ? config : null,
props: maybeKey
};
}
reactJsxRuntime_production.Fragment = REACT_FRAGMENT_TYPE;
reactJsxRuntime_production.jsx = jsxProd;
reactJsxRuntime_production.jsxs = jsxProd;
return reactJsxRuntime_production;
}
var hasRequiredJsxRuntime;
function requireJsxRuntime() {
if (hasRequiredJsxRuntime) return jsxRuntime.exports;
hasRequiredJsxRuntime = 1;
{
jsxRuntime.exports = requireReactJsxRuntime_production();
}
return jsxRuntime.exports;
}
var jsxRuntimeExports = requireJsxRuntime();
function TableHeader({
table,
className = "",
customRenderer
}) {
const [resizeState, setResizeState] = useState({
isResizing: false,
startX: 0,
startWidth: 0,
header: null,
currentWidth: 0
});
const tableRef = useRef(null);
const resizeTimeoutRef = useRef(void 0);
const handleResize = useCallback((header, newWidth) => {
var _a;
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
const headerElement = (_a = tableRef.current) == null ? void 0 : _a.querySelector(`[data-column-id="${header.id}"]`);
if (headerElement) {
headerElement.style.width = `${newWidth}px`;
}
resizeTimeoutRef.current = window.setTimeout(() => {
table.setColumnSizing((prev) => ({
...prev,
[header.column.id]: newWidth
}));
}, 16);
}, [table]);
const handleMouseMove = useCallback((e) => {
if (!resizeState.isResizing || !resizeState.header) return;
e.preventDefault();
e.stopPropagation();
const deltaX = e.clientX - resizeState.startX;
const newWidth = Math.max(
resizeState.header.column.columnDef.minSize ?? 50,
Math.min(
resizeState.header.column.columnDef.maxSize ?? 1e3,
resizeState.startWidth + deltaX
)
);
setResizeState((prev) => ({ ...prev, currentWidth: newWidth }));
handleResize(resizeState.header, newWidth);
}, [resizeState.isResizing, resizeState.header, resizeState.startX, resizeState.startWidth, handleResize]);
const handleMouseUp = useCallback(() => {
if (resizeState.isResizing) {
if (resizeState.header && resizeState.currentWidth > 0) {
table.setColumnSizing((prev) => ({
...prev,
[resizeState.header.column.id]: resizeState.currentWidth
}));
}
setResizeState((prev) => ({
...prev,
isResizing: false,
header: null,
currentWidth: 0
}));
document.body.style.userSelect = "";
document.body.style.cursor = "";
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
resizeTimeoutRef.current = void 0;
}
if (tableRef.current) {
const table2 = tableRef.current.closest(".datagrid-table");
if (table2) {
table2.removeAttribute("data-resizing");
}
}
}
}, [resizeState.isResizing, resizeState.header, resizeState.currentWidth, table]);
const handleTouchMove = useCallback((e) => {
if (!resizeState.isResizing || !resizeState.header) return;
e.preventDefault();
const touch = e.touches[0];
if (touch) {
const deltaX = touch.clientX - resizeState.startX;
const newWidth = Math.max(
resizeState.header.column.columnDef.minSize ?? 50,
Math.min(
resizeState.header.column.columnDef.maxSize ?? 1e3,
resizeState.startWidth + deltaX
)
);
setResizeState((prev) => ({ ...prev, currentWidth: newWidth }));
handleResize(resizeState.header, newWidth);
}
}, [resizeState.isResizing, resizeState.header, resizeState.startX, resizeState.startWidth, handleResize]);
const handleTouchEnd = useCallback(() => {
handleMouseUp();
}, [handleMouseUp]);
useEffect(() => {
if (resizeState.isResizing) {
document.addEventListener("mousemove", handleMouseMove, { passive: false });
document.addEventListener("mouseup", handleMouseUp, { passive: false });
document.addEventListener("touchmove", handleTouchMove, { passive: false });
document.addEventListener("touchend", handleTouchEnd, { passive: false });
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("touchmove", handleTouchMove);
document.removeEventListener("touchend", handleTouchEnd);
};
}
}, [resizeState.isResizing, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
useEffect(() => {
return () => {
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
};
}, []);
const handleResizeStart = useCallback((e, header) => {
var _a;
e.preventDefault();
e.stopPropagation();
const clientX = "touches" in e ? (_a = e.touches[0]) == null ? void 0 : _a.clientX : e.clientX;
if (typeof clientX !== "number") return;
const currentWidth = header.getSize();
setResizeState({
isResizing: true,
startX: clientX,
startWidth: currentWidth,
header,
currentWidth
});
document.body.style.userSelect = "none";
document.body.style.cursor = "col-resize";
if (tableRef.current) {
const table2 = tableRef.current.closest(".datagrid-table");
if (table2) {
table2.setAttribute("data-resizing", "true");
}
}
}, []);
return /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { ref: tableRef, className: `datagrid-header ${className}`, children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: "datagrid-header-row", children: headerGroup.headers.map((header) => {
var _a, _b, _c, _d, _e;
const column = header.column.columnDef;
const canSort = header.column.getCanSort();
const sortDirection = header.column.getIsSorted();
const isColumnResizing = resizeState.isResizing && ((_a = resizeState.header) == null ? void 0 : _a.id) === header.id;
const isAnyColumnResizing = resizeState.isResizing;
const headerConfig = column.headerConfig;
const headerClassName = `datagrid-header-cell ${canSort ? "sortable" : ""} ${sortDirection ? `sorted-${sortDirection}` : ""} ${isColumnResizing ? "resizing" : ""} ${isAnyColumnResizing ? "table-resizing" : ""} ${(headerConfig == null ? void 0 : headerConfig.className) || ""}`;
const displayWidth = isColumnResizing && resizeState.currentWidth > 0 ? resizeState.currentWidth : header.getSize();
const headerStyle = {
width: displayWidth,
minWidth: header.column.columnDef.minSize,
maxWidth: header.column.columnDef.maxSize,
textAlign: (headerConfig == null ? void 0 : headerConfig.align) || "left",
...headerConfig == null ? void 0 : headerConfig.style
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"th",
{
"data-column-id": header.id,
className: headerClassName,
style: headerStyle,
onClick: canSort && !isAnyColumnResizing ? header.column.getToggleSortingHandler() : void 0,
title: headerConfig == null ? void 0 : headerConfig.tooltip,
"data-resizing": isColumnResizing ? "true" : "false",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-header-content", children: customRenderer && customRenderer(column) || /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
header.isPlaceholder ? null : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "datagrid-header-text", children: [
(headerConfig == null ? void 0 : headerConfig.icon) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-header-icon", style: { marginRight: "6px" }, children: headerConfig.icon }),
flexRender(header.column.columnDef.header, header.getContext())
] }),
canSort && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "datagrid-sort-indicator", children: [
sortDirection === "asc" && /* @__PURE__ */ jsxRuntimeExports.jsx(
"svg",
{
className: "sort-asc",
width: "12",
height: "12",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m7 14 5-5 5 5" })
}
),
sortDirection === "desc" && /* @__PURE__ */ jsxRuntimeExports.jsx(
"svg",
{
className: "sort-desc",
width: "12",
height: "12",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m17 10-5 5-5-5" })
}
),
!sortDirection && /* @__PURE__ */ jsxRuntimeExports.jsxs(
"svg",
{
className: "sort-none",
width: "12",
height: "12",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "1.5",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m7 14 5-5 5 5" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m7 10 5 5 5-5" })
]
}
)
] })
] }) }),
header.column.getCanFilter() && ((_b = column.filterConfig) == null ? void 0 : _b.enabled) === true && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `datagrid-column-filter ${((_c = column.filterConfig) == null ? void 0 : _c.className) || ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: ((_d = column.filterConfig) == null ? void 0 : _d.type) || "text",
value: header.column.getFilterValue() ?? "",
onChange: (e) => header.column.setFilterValue(e.target.value),
placeholder: ((_e = column.filterConfig) == null ? void 0 : _e.placeholder) || `Filter ${header.id}...`,
className: "datagrid-filter-input",
onClick: (e) => e.stopPropagation()
}
) }),
header.column.getCanResize() && /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: `datagrid-column-resizer ${isColumnResizing ? "active" : ""}`,
onMouseDown: (e) => handleResizeStart(e, header),
onTouchStart: (e) => handleResizeStart(e, header),
"aria-label": `Resize ${header.id} column`,
role: "separator",
tabIndex: -1,
children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-column-resizer-handle" })
}
)
]
},
header.id
);
}) }, headerGroup.id)) });
}
function ContextMenu({
items,
position,
onClose,
cellValue,
rowData,
className = ""
}) {
const menuRef = useRef(null);
const [adjustedPosition, setAdjustedPosition] = useState(position);
useEffect(() => {
const handleClickOutside = (event) => {
if (menuRef.current && !menuRef.current.contains(event.target)) {
onClose();
}
};
const handleEscape = (event) => {
if (event.key === "Escape") {
onClose();
}
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleEscape);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleEscape);
};
}, [onClose]);
useEffect(() => {
if (menuRef.current) {
const menu = menuRef.current;
const rect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let { x, y } = position;
if (x + rect.width > viewportWidth) {
x = viewportWidth - rect.width - 10;
}
if (y + rect.height > viewportHeight) {
y = viewportHeight - rect.height - 10;
}
setAdjustedPosition({ x, y });
}
}, [position]);
const handleItemClick = (item) => {
if (typeof item.disabled === "function" ? item.disabled(cellValue, rowData) : item.disabled) {
return;
}
item.onClick(cellValue, rowData);
onClose();
};
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
ref: menuRef,
className: `datagrid-context-menu ${className}`,
style: {
position: "fixed",
left: adjustedPosition.x,
top: adjustedPosition.y,
zIndex: 9999
},
children: items.map((item, index) => {
if (item.separator) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-context-menu-separator" }, index);
}
const isDisabled = typeof item.disabled === "function" ? item.disabled(cellValue, rowData) : item.disabled;
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
className: `datagrid-context-menu-item ${isDisabled ? "disabled" : ""} ${item.className || ""}`,
onClick: () => handleItemClick(item),
children: [
item.icon && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-context-menu-icon", children: item.icon }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-context-menu-label", children: item.label })
]
},
index
);
})
}
);
}
function TableBody({
table,
className = "",
rowClassName = "",
cellClassName = "",
customCellRenderer,
onRowClick,
onRowDoubleClick,
onCellClick
}) {
var _a;
const rows = table.getRowModel().rows;
const [contextMenu, setContextMenu] = useState(null);
const getRowClassName = (row) => {
if (typeof rowClassName === "function") {
return `datagrid-body-row ${rowClassName(row)}`;
}
return `datagrid-body-row ${rowClassName}`;
};
const getCellClassName = (cell, row, column) => {
if (typeof cellClassName === "function") {
return `datagrid-body-cell ${cellClassName(cell, row, column)}`;
}
return `datagrid-body-cell ${cellClassName}`;
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { className: `datagrid-body ${className}`, children: [
rows.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsx(
"tr",
{
className: getRowClassName(row.original),
onClick: () => onRowClick == null ? void 0 : onRowClick(row.original),
onDoubleClick: () => onRowDoubleClick == null ? void 0 : onRowDoubleClick(row.original),
children: row.getVisibleCells().map((cell) => {
const column = cell.column.columnDef;
const cellConfig = column.cellConfig;
const cellValue = cell.getValue();
let cellClassNames = getCellClassName(cellValue, row.original, column);
if (cellConfig == null ? void 0 : cellConfig.className) {
if (typeof cellConfig.className === "function") {
cellClassNames += ` ${cellConfig.className(cellValue, row.original)}`;
} else {
cellClassNames += ` ${cellConfig.className}`;
}
}
let cellStyle = {
width: cell.column.getSize(),
minWidth: cell.column.columnDef.minSize,
maxWidth: cell.column.columnDef.maxSize,
textAlign: (cellConfig == null ? void 0 : cellConfig.align) || "left"
};
if (cellConfig == null ? void 0 : cellConfig.style) {
if (typeof cellConfig.style === "function") {
cellStyle = { ...cellStyle, ...cellConfig.style(cellValue, row.original) };
} else {
cellStyle = { ...cellStyle, ...cellConfig.style };
}
}
let cellContent = customCellRenderer ? customCellRenderer(cellValue, row.original, column) : (cellConfig == null ? void 0 : cellConfig.format) ? cellConfig.format(cellValue, row.original) : flexRender(cell.column.columnDef.cell, cell.getContext());
let cellTitle;
if (cellConfig == null ? void 0 : cellConfig.tooltip) {
if (typeof cellConfig.tooltip === "function") {
cellTitle = cellConfig.tooltip(cellValue, row.original);
} else if (cellConfig.tooltip === true) {
cellTitle = String(cellValue);
}
}
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"td",
{
className: cellClassNames,
style: cellStyle,
title: cellTitle,
onClick: (e) => {
e.stopPropagation();
if (cellConfig == null ? void 0 : cellConfig.onClick) {
cellConfig.onClick(cellValue, row.original);
} else {
onCellClick == null ? void 0 : onCellClick(cellValue, row.original);
}
},
onDoubleClick: (e) => {
e.stopPropagation();
if (cellConfig == null ? void 0 : cellConfig.onDoubleClick) {
cellConfig.onDoubleClick(cellValue, row.original);
}
},
onContextMenu: (e) => {
var _a2;
e.preventDefault();
e.stopPropagation();
if (cellConfig == null ? void 0 : cellConfig.onRightClick) {
cellConfig.onRightClick(cellValue, row.original, e);
}
if (((_a2 = cellConfig == null ? void 0 : cellConfig.contextMenu) == null ? void 0 : _a2.items) && cellConfig.contextMenu.items.length > 0) {
setContextMenu({
visible: true,
position: { x: e.clientX, y: e.clientY },
cellValue,
rowData: row.original,
column
});
}
},
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: `datagrid-cell-content ${(cellConfig == null ? void 0 : cellConfig.wrap) ? "wrap" : ""} ${(cellConfig == null ? void 0 : cellConfig.truncate) ? "truncate" : ""}`,
children: cellContent
}
)
},
cell.id
);
})
},
row.id
)),
rows.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: "datagrid-empty-row", children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: table.getAllColumns().length, className: "datagrid-empty-cell", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-empty-content", children: "No data available" }) }) })
] }),
(contextMenu == null ? void 0 : contextMenu.visible) && ((_a = contextMenu.column.cellConfig) == null ? void 0 : _a.contextMenu) && /* @__PURE__ */ jsxRuntimeExports.jsx(
ContextMenu,
{
items: contextMenu.column.cellConfig.contextMenu.items,
position: contextMenu.position,
onClose: () => setContextMenu(null),
cellValue: contextMenu.cellValue,
rowData: contextMenu.rowData,
className: contextMenu.column.cellConfig.contextMenu.className
}
)
] });
}
function TablePagination({
table,
pageSizeOptions = [5, 10, 20, 50, 100]
}) {
const pageIndex = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
const pageCount = table.getPageCount();
const isManualPagination = table.options.manualPagination;
const totalRows = isManualPagination ? table.options.rowCount ?? 0 : table.getFilteredRowModel().rows.length;
const startRow = totalRows > 0 ? pageIndex * pageSize + 1 : 0;
const endRow = totalRows > 0 ? Math.min((pageIndex + 1) * pageSize, totalRows) : 0;
const canPreviousPage = table.getCanPreviousPage();
const canNextPage = table.getCanNextPage();
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-pagination", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-pagination-info", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "datagrid-pagination-text", children: [
"Showing ",
startRow,
" to ",
endRow,
" of ",
totalRows,
" entries"
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-pagination-controls", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-page-size", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "page-size-select", className: "datagrid-page-size-label", children: "Show:" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"select",
{
id: "page-size-select",
value: pageSize,
onChange: (e) => table.setPageSize(Number(e.target.value)),
className: "datagrid-page-size-select",
children: pageSizeOptions.map((size) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: size, children: size }, size))
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-page-navigation", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: () => table.setPageIndex(0),
disabled: !canPreviousPage,
className: "datagrid-page-button",
title: "First page",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
"svg",
{
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m11 17-5-5 5-5" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m18 17-5-5 5-5" })
]
}
)
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: () => table.previousPage(),
disabled: !canPreviousPage,
className: "datagrid-page-button",
title: "Previous page",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
"svg",
{
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m15 18-6-6 6-6" })
}
)
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-page-info", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "datagrid-page-current", children: [
"Page ",
pageIndex + 1,
" of ",
pageCount
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: () => table.nextPage(),
disabled: !canNextPage,
className: "datagrid-page-button",
title: "Next page",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
"svg",
{
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m9 18 6-6-6-6" })
}
)
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: () => table.setPageIndex(pageCount - 1),
disabled: !canNextPage,
className: "datagrid-page-button",
title: "Last page",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
"svg",
{
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m6 17 5-5-5-5" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m13 17 5-5-5-5" })
]
}
)
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-page-jump", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "page-jump-input", className: "datagrid-page-jump-label", children: "Go to:" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
id: "page-jump-input",
type: "number",
min: 1,
max: pageCount,
defaultValue: pageIndex + 1,
onChange: (e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
table.setPageIndex(page);
},
className: "datagrid-page-jump-input"
}
)
] })
] })
] });
}
function GlobalFilter({
value,
onChange,
placeholder = "Search all columns...",
className = "",
showIcon = true,
debounceMs = 300
}) {
const [localValue, setLocalValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
onChange(localValue);
}, debounceMs);
return () => clearTimeout(timer);
}, [localValue, onChange, debounceMs]);
useEffect(() => {
setLocalValue(value);
}, [value]);
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `datagrid-global-filter ${className}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-search-container", children: [
showIcon && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-search-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
"svg",
{
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "11", cy: "11", r: "8" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m21 21-4.35-4.35" })
]
}
) }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: "text",
value: localValue,
onChange: (e) => setLocalValue(e.target.value),
placeholder,
className: "datagrid-search-input",
style: { paddingLeft: showIcon ? "36px" : "12px" }
}
)
] }) });
}
function TableContextMenu({
items,
position,
onClose,
data,
className = ""
}) {
const menuRef = useRef(null);
const [adjustedPosition, setAdjustedPosition] = useState(position);
useEffect(() => {
const handleClickOutside = (event) => {
if (menuRef.current && !menuRef.current.contains(event.target)) {
onClose();
}
};
const handleEscape = (event) => {
if (event.key === "Escape") {
onClose();
}
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleEscape);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleEscape);
};
}, [onClose]);
useEffect(() => {
if (menuRef.current) {
const menu = menuRef.current;
const rect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let { x, y } = position;
if (x + rect.width > viewportWidth) {
x = viewportWidth - rect.width - 10;
}
if (y + rect.height > viewportHeight) {
y = viewportHeight - rect.height - 10;
}
setAdjustedPosition({ x, y });
}
}, [position]);
const handleItemClick = (item) => {
if (typeof item.disabled === "function" ? item.disabled(data) : item.disabled) {
return;
}
item.onClick(data);
onClose();
};
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
ref: menuRef,
className: `datagrid-context-menu ${className}`,
style: {
position: "fixed",
left: adjustedPosition.x,
top: adjustedPosition.y,
zIndex: 9999
},
children: items.map((item, index) => {
if (item.separator) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-context-menu-separator" }, index);
}
const isDisabled = typeof item.disabled === "function" ? item.disabled(data) : item.disabled;
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
className: `datagrid-context-menu-item ${isDisabled ? "disabled" : ""} ${item.className || ""}`,
onClick: () => handleItemClick(item),
children: [
item.icon && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-context-menu-icon", children: item.icon }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-context-menu-label", children: item.label })
]
},
index
);
})
}
);
}
const FloatingActionDock = ({
selectedData,
selectedRowIndices,
items,
className = "",
position = "bottom-center",
showCount = true,
hideDelay = 0,
onClose
}) => {
const [isVisible, setIsVisible] = useState(false);
const [isLeaving, setIsLeaving] = useState(false);
const hasSelectionRef = useRef(false);
useEffect(() => {
if (selectedData.length > 0) {
if (!hasSelectionRef.current) {
hasSelectionRef.current = true;
setIsVisible(true);
}
setIsLeaving(false);
} else if (hasSelectionRef.current && !isLeaving) {
setIsLeaving(true);
const timer = setTimeout(() => {
setIsVisible(false);
setIsLeaving(false);
hasSelectionRef.current = false;
if (onClose) onClose();
}, 220);
return () => clearTimeout(timer);
}
}, [selectedData.length, onClose, isLeaving]);
if (!hasSelectionRef.current && !isVisible) return null;
const getPositionClasses = () => {
switch (position) {
case "bottom-left":
return "dock-position-bottom-left";
case "bottom-right":
return "dock-position-bottom-right";
case "bottom-center":
return "dock-position-bottom-center";
case "top-left":
return "dock-position-top-left";
case "top-right":
return "dock-position-top-right";
case "top-center":
return "dock-position-top-center";
default:
return "dock-position-bottom-center";
}
};
const handleItemClick = (item) => {
if (item.disabled) {
const isDisabled = typeof item.disabled === "function" ? item.disabled(selectedData, selectedRowIndices) : item.disabled;
if (isDisabled) return;
}
item.onClick(selectedData, selectedRowIndices);
};
const getVariantClasses = (variant) => {
switch (variant) {
case "primary":
return "variant-primary";
case "danger":
return "variant-danger";
case "success":
return "variant-success";
default:
return "variant-default";
}
};
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: `datagrid-floating-dock ${getPositionClasses()} ${className}`,
"data-entering": selectedData.length > 0 && !hasSelectionRef.current ? "true" : "false",
"data-leaving": isLeaving ? "true" : "false",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "datagrid-floating-dock-container", children: [
showCount && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-floating-dock-badge", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
selectedData.length,
" selected"
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "datagrid-floating-dock-actions", children: items.map((item, index) => {
if (item.separator) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: "datagrid-floating-dock-separator"
},
`separator-${index}`
);
}
const isDisabled = item.disabled ? typeof item.disabled === "function" ? item.disabled(selectedData, selectedRowIndices) : item.disabled : false;
if (item.customButton) {
const CustomButton = item.customButton;
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
CustomButton,
{
onClick: () => handleItemClick(item),
disabled: isDisabled,
className: `datagrid-floating-dock-button ${item.className || ""}`,
children: [
item.icon && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-floating-dock-button-icon", children: item.icon }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-floating-dock-button-label", children: item.label })
]
},
index
);
}
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"button",
{
onClick: () => handleItemClick(item),
disabled: isDisabled,
className: `datagrid-floating-dock-button ${getVariantClasses(item.variant)} ${item.className || ""} ${isDisabled ? "datagrid-floating-dock-button-disabled" : ""}`,
title: item.label,
children: [
item.icon && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-floating-dock-button-icon", children: item.icon }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "datagrid-floating-dock-button-label", children: item.label })
]
},
index
);
}) })
] })
}
);
};
const DataGridComponent = function DataGrid2({
data,
columns,
enableSorting = true,
enableFiltering = true,
enablePagination = true,
enableRowSelection = false,
enableColumnResizing = true,
enableGlobalSearch = true,
pageSize = 10,
pageSizeOptions = [5, 10, 20, 50, 100],
rowCount,
enableMultiRowSelection = true,
density = "comfortable",
theme = "light",
sorting: controlledSorting,
onSortingChange,
columnFilters: controlledColumnFilters,
onColumnFiltersChange,
pagination: controlledPagination,
onPaginationChange,
rowSelection: controlledRowSelection,
onRowSelectionChange,
globalFilter: controlledGlobalFilter,
onGlobalFilterChange,
searchConfig = {
enabled: true,
placeholder: "Search all columns...",
position: "top",
showIcon: true,
debounceMs: 300
},
tableContextMenu,
onTableRightClick,
floatingActionDock,
onRowClick,
onRowDoubleClick,
onCellClick,
loading = false,
loadingComponent: LoadingComponent,
emptyComponent: EmptyComponent,
errorComponent: _ErrorComponent,
customCellRenderer,
customHeaderRenderer,
className = "",
tableClassName = "",
headerClassName = "",
bodyClassName = "",
rowClassName = "",
cellClassName = "",
// Advanced features
enableInlineEditing: _enableInlineEditing = false,
enableExpandableRows: _enableExpandableRows = false,
enableGrouping: _enableGrouping = false,
enableAggregation: _enableAggregation = false,
enableExport: _enableExport = false,
// Server-side operations
manualSorting = false,
manualFiltering = false,
manualPagination = false
}) {
const [internalSorting, setInternalSorting] = useState([]);
const [internalColumnFilters, setInternalColumnFilters] = useState([]);
const [internalPagination, setInternalPagination] = useState({
pageIndex: 0,
pageSize
});
const [internalRowSelection, setInternalRowSelection] = useState({});
const [internalGlobalFilter, setInternalGlobalFilter] = useState("");
const [tableContextMenuState, setTableContextMenuState] = useState(null);
const tableContainerRef = useRef(null);
const [hasHorizontalOverflow, setHasHorizontalOverflow] = useState(false);
const sorting = controlledSorting ?? internalSorting;
const columnFilters = controlledColumnFilters ?? internalColumnFilters;
const pagination = controlledPagination ?? internalPagination;
const rowSelection = controlledRowSelection ?? internalRowSelection;
const globalFilter = controlledGlobalFilter ?? internalGlobalFilter;
const processedColumns = useMemo(() => {
return columns.map((col) => ({
...col,
enableSorting: col.enableSorting ?? enableSorting,
enableColumnFilter: col.enableFiltering ?? enableFiltering,
enableResizing: col.enableResizing ?? enableColumnResizing,
enableHiding: col.enableHiding ?? true,
size: col.size ?? 150,
minSize: col.minSize ?? 50,
maxSize: col.maxSize ?? 500
}));
}, [columns, enableSorting, enableFiltering, enableColumnResizing]);
const finalColumns = useMemo(() => {
if (!enableRowSelection) return processedColumns;
const selectionColumn = {
id: "select",
accessorKey: "select",
header: ({ table: table2 }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: "checkbox",
checked: table2.getIsAllRowsSelected(),
onChange: table2.getToggleAllRowsSelectedHandler(),
className: "datagrid-checkbox"
}
),
cell: ({ row }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: "checkbox",
checked: row.getIsSelected(),
onChange: row.getToggleSelectedHandler(),
className: "datagrid-checkbox"
}
),
size: 50,
enableSorting: false,
enableFiltering: false,
enableResizing: false,
enableHiding: false
};
return [selectionColumn, ...processedColumns];
}, [processedColumns, enableRowSelection]);
const table = useReactTable({
data,
columns: finalColumns,
// Cast to ColumnDef to match TanStack Table requirements
state: {
sorting,
columnFilters,
pagination,
rowSelection,
globalFilter
},
enableRowSelection,
enableColumnResizing,
columnResizeMode: "onEnd",
// Use onEnd mode to prevent excessive re-renders during drag
columnResizeDirection: "ltr",
onSortingChange: onSortingChange || setInternalSorting,
onColumnFiltersChange: onColumnFiltersChange || setInternalColumnFilters,
onPaginationChange: onPaginationChange || setInternalPagination,
onRowSelectionChange: onRowSelectionChange || setInternalRowSelection,
onGlobalFilterChange: onGlobalFilterChange || setInternalGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: enablePagination ? getPaginationRowModel() : void 0,
manualSorting,
manualFiltering,
manualPagination,
autoResetPageIndex: false,
rowCount: manualPagination ? rowCount : void 0
});
const containerClasses = `datagrid-container datagrid-density-${density} datagrid-theme-${theme} ${className}`;
const tableClasses = `datagrid-table ${tableClassName}`;
const handleTableRightClick = useCallback(
(event) => {
event.preventDefault();
if (onTableRightClick) {
onTableRightClick(event);
}
if ((tableContextMenu == null ? void 0 : tableContextMenu.items) && tableContextMenu.items.length > 0) {
setTableContextMenuState({
visible: true,
position: { x: event.clientX, y: event.clientY }
});
}
},
[onTableRightClick, tableContextMenu]
);
useEffect(() => {
const checkOverflow = () => {
if (tableContainerRef.current) {
const container = tableContainerRef.current;
const hasOverflow = container.scrollWidth > container.clientWidth + 5;
setHasHorizontalOverflow(hasOverflow);
}
};
const timeoutId = setTimeout(checkOverflow, 100);
window.addEventListener("resize", checkOverflow);
const observer = new ResizeObserver(() => {
setTimeout(checkOverflow, 50);
});
if (tableContainerRef.current) {
observer.observe(tableContainerRef.current);
}
return () => {
clearTimeout(timeoutId);
window.removeEventListener("resize", checkOverflow);
observer.disconnect();
};
}, [data, finalColumns]);
const tableContent = /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
ref: tableContainerRef,
className: `datagrid-table-container ${hasHorizontalOverflow ? "overflow-content" : "fits-content"}`,
onContextMenu: handleTableRightClick,
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
"table",
{
className: tableClasses,
"data-resizing": table.getState().columnSizingInfo.isResizingColumn ? "true" : "false",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(TableHeader, { table, className: headerClassName, customRenderer: customHeaderRenderer }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
TableBody,
{
table,
className: bodyClassName,
rowClassName,
cellClassName,
customCellRenderer,
onRowClick,
onRowDoubleClick,
onCellClick
}
)
]
}
)
}
);
const renderSearch = () => {
if (!enableGlobalSearch || !(searchConfig == null ? void 0 : searchConfig.enabled) || (searchConfig == null ? void 0 : searchConfig.position) === "none") {
return null;
}
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `datagrid-toolbar ${(searchConfig == null ? void 0 : searchConfig.className) || ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
GlobalFilter,
{
value: globalFilter,
onChange: setInternalGlobalFilter,
placeholder: (searchConfig == null ? void 0 : searchConfig.placeholder) || "Search all columns...",
showIcon: searchConfig == null ? void 0 : searchConfig.showIcon,
debounceMs: searchConfig == null ? void 0 : searchConfig.debounceMs
}
) });
};
const selectedRowIndices = Object.keys(rowSelection).filter((key) => rowSelection[key]).map(Number);
const selectedData = selectedRowIndices.map((index) => data[index]).filter(Boolean);
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: containerClasses, children: [
(searchConfig == null ? void 0 : searchConfig.position) === "top" && renderSearch(),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "relative" }, children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
position: "absolute",
top: 0,
left: 0,
right: 0,
height: "3px",
backgroundColor: loading ? "rgba(229, 231, 235, 0.3)" : "transparent",
zIndex: 1e3,
overflow: "hidden",
borderRadius: "0px",
transition: "backgroundColor 0.2s ease-in-out",
opacity: loading ? 1 : 0
}, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
position: "absolute",
top: 0,
left: loading ? "-100%" : "100%",
width: "100%",
height: "100%",
background: "linear-gradient(90deg, transparent 0%, #3b82f6 50%, transparent 100%)",
animation: loading ? "line-bar-loader-slide 1.5s ease-in-out infinite" : "none",
transition: "left 0.3s ease-in-out"
} }) }),
tableContent
] }),
(searchConfig == null ? void 0 : searchConfig.position) === "bottom" && renderSearch(),
enablePagination && /* @__PURE__ */ jsxRuntimeExports.jsx(TablePagination, { table, pageSizeOptions }),
(tableContextMenuState == null ? void 0 : tableContextMenuState.visible) && tableContextMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(
TableContextMenu,
{
items: tableContextMenu.items,
position: tableContextMenuState.position,
onClose: () => setTableContextMenuState(null),
data,
className: tableContextMenu.className
}
),
(floatingActionDock == null ? void 0 : floatingActionDock.enabled) && floatingActionDock.items.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
FloatingActionDock,
{
selectedData,
selectedRowIndices,
items: floatingActionDock.items,
position: floatingActionDock.position,
showCount: floatingActionDock.showCount,
hideDelay: floatingActionDock.hideDelay,
className: floatingActionDock.className
}
)
] });
};
const DataGrid = memo(DataGridComponent);
const createCopyValueAction = (label = "Copy Value", icon = "📋") => ({
label,
icon,
onClick: async (value) => {
try {
await navigator.clipboard.writeText(String(value));
console.log("Value copied to clipboard:", value);
} catch (err) {
console.error("Failed to copy value:", err);
const textArea = document.createElement("textarea");
textArea.value = String(value);
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
}
});
const createCopyRowAction = (label = "Copy Row", icon = "📄") => ({
label,
icon,
onClick: async (value, row) => {
try {
const jsonString = JSON.stringify(row, null, 2);
await navigator.clipboard.writeText(jsonString);
console.log("Row copied to clipboard:", row);
} catch (err) {
console.error("Failed to copy row:", err);
}
}
});
const createCopyFormattedAction = (formatter, label = "Copy Formatted", icon = "📋") => ({
label,
icon,
onClick: async (value, row) => {
try {
const formattedValue = formatter(value, row);
await navigator.clipboard.writeText(formattedValue);
console.log("Formatted value copied:", formattedValue);
} catch (err) {
console.error("Failed to copy formatted value:", err);
}
}
});
const createEmailAction = (label = "Send Email", icon = "📧") => ({
label,
icon,
onClick: (value) => {
window.open(`mailto:${value}`);
}
});
const createPhoneAction = (label = "Call", icon = "📞") => ({
label,
icon,
onClick: (value) => {
window.open(`tel:${value}`);
}
});
const createOpenUrlAction = (label = "Open Link", icon = "🔗") => ({
label,
icon,
onClick: (value) => {
window.open(value, "_blank");
}
});
const creat