@rufous/ui 0.1.66 → 0.1.68

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.
@@ -36,18 +36,24 @@ module.exports = __toCommonJS(DataGrid_exports);
36
36
  var import_react = __toESM(require("react"), 1);
37
37
  var import_lucide_react = require("lucide-react");
38
38
  function DataGrid({
39
- columns: initialColumns,
39
+ columns: initialColumnsProp,
40
40
  data,
41
41
  actions,
42
42
  pageSize: initialPageSize = 10,
43
43
  pageSizeOptions = [5, 10, 25, 50],
44
44
  title
45
45
  }) {
46
+ const initialColumns = (0, import_react.useMemo)(() => initialColumnsProp.map((col) => ({
47
+ ...col,
48
+ key: col.key || col.field || "",
49
+ header: col.header || col.headerName || ""
50
+ })), [initialColumnsProp]);
46
51
  const [columns, setColumns] = (0, import_react.useState)(initialColumns);
47
52
  const [columnWidths, setColumnWidths] = (0, import_react.useState)(() => {
48
53
  const widths = {};
49
54
  initialColumns.forEach((col) => {
50
- widths[String(col.key)] = parseInt(col.width || "200");
55
+ const w = col.width || 200;
56
+ widths[String(col.key)] = typeof w === "number" ? w : parseInt(w);
51
57
  });
52
58
  return widths;
53
59
  });
@@ -66,16 +72,42 @@ function DataGrid({
66
72
  const [showManageColumns, setShowManageColumns] = (0, import_react.useState)(false);
67
73
  const [showAdvancedFilter, setShowAdvancedFilter] = (0, import_react.useState)(false);
68
74
  const [advancedFilters, setAdvancedFilters] = (0, import_react.useState)([
69
- { column: String(initialColumns[0].key), operator: "contains", value: "", logic: "AND" }
75
+ { column: String(initialColumns[0]?.key || ""), operator: "contains", value: "", logic: "AND" }
70
76
  ]);
77
+ (0, import_react.useEffect)(() => {
78
+ setColumns((prevColumns) => {
79
+ return initialColumns.map((newCol) => {
80
+ const prevCol = prevColumns.find((c) => (c.key || c.field) === (newCol.key || newCol.field));
81
+ if (!prevCol) return newCol;
82
+ return {
83
+ ...newCol,
84
+ hidden: prevCol.hidden !== void 0 ? prevCol.hidden : newCol.hidden,
85
+ pinned: prevCol.pinned !== void 0 ? prevCol.pinned : newCol.pinned
86
+ };
87
+ });
88
+ });
89
+ }, [initialColumns]);
90
+ (0, import_react.useEffect)(() => {
91
+ setColumnWidths((prev) => {
92
+ const next = { ...prev };
93
+ initialColumns.forEach((col) => {
94
+ const key = String(col.key);
95
+ if (next[key] === void 0) {
96
+ const w = col.width || 200;
97
+ next[key] = typeof w === "number" ? w : parseInt(w);
98
+ }
99
+ });
100
+ return next;
101
+ });
102
+ }, [initialColumns]);
71
103
  const [colSearch, setColSearch] = (0, import_react.useState)("");
72
104
  (0, import_react.useEffect)(() => {
73
105
  const handleMouseMove = (e) => {
74
106
  if (!resizingColumn) return;
75
107
  const col = columns.find((c) => String(c.key) === resizingColumn);
76
108
  const diff = e.clientX - startX;
77
- const minW = col?.minWidth ? parseInt(col.minWidth) : 80;
78
- const maxW = col?.maxWidth ? parseInt(col.maxWidth) : Infinity;
109
+ const minW = col?.minWidth ? typeof col.minWidth === "number" ? col.minWidth : parseInt(col.minWidth) : 80;
110
+ const maxW = col?.maxWidth ? typeof col.maxWidth === "number" ? col.maxWidth : parseInt(col.maxWidth) : Infinity;
79
111
  const newWidth = Math.min(maxW, Math.max(minW, startWidth + diff));
80
112
  setColumnWidths((prev) => ({ ...prev, [resizingColumn]: newWidth }));
81
113
  };
@@ -166,14 +198,19 @@ function DataGrid({
166
198
  }, [data, filterText, advancedFilters]);
167
199
  const sortedData = (0, import_react.useMemo)(() => {
168
200
  if (!sortKey || !sortDirection) return filteredData;
201
+ const col = columns.find((c) => c.key === sortKey);
169
202
  return [...filteredData].sort((a, b) => {
170
- const aVal = a[sortKey];
171
- const bVal = b[sortKey];
203
+ let aVal = a[sortKey];
204
+ let bVal = b[sortKey];
205
+ if (col?.valueGetter) {
206
+ aVal = col.valueGetter({ value: aVal, row: a, field: String(sortKey) });
207
+ bVal = col.valueGetter({ value: bVal, row: b, field: String(sortKey) });
208
+ }
172
209
  if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
173
210
  if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
174
211
  return 0;
175
212
  });
176
- }, [filteredData, sortKey, sortDirection]);
213
+ }, [filteredData, sortKey, sortDirection, columns]);
177
214
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
178
215
  const paginatedData = (0, import_react.useMemo)(() => {
179
216
  const start = (currentPage - 1) * pageSize;
@@ -253,8 +290,8 @@ function DataGrid({
253
290
  "th",
254
291
  {
255
292
  key: colKey,
256
- className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""}`,
257
- style: { width, minWidth: width, left: leftOffset }
293
+ className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""} ${col.headerClassName || ""}`,
294
+ style: { width, minWidth: width, left: leftOffset, flex: col.flex }
258
295
  },
259
296
  /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-th-inner" }, /* @__PURE__ */ import_react.default.createElement(
260
297
  "div",
@@ -265,7 +302,7 @@ function DataGrid({
265
302
  col.header,
266
303
  isSorted && sortDirection === "asc" && /* @__PURE__ */ import_react.default.createElement(import_lucide_react.ChevronUp, { size: 12 }),
267
304
  isSorted && sortDirection === "desc" && /* @__PURE__ */ import_react.default.createElement(import_lucide_react.ChevronDown, { size: 12 })
268
- ), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-th-actions" }, /* @__PURE__ */ import_react.default.createElement(
305
+ ), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-th-actions" }, !col.disableColumnMenu && /* @__PURE__ */ import_react.default.createElement(
269
306
  "button",
270
307
  {
271
308
  className: "dg-th-menu-btn",
@@ -293,22 +330,39 @@ function DataGrid({
293
330
  "td",
294
331
  {
295
332
  key: `${item.id}-${colKey}`,
296
- className: `dg-td${col.pinned === "left" ? " pinned-left" : ""}`,
297
- style: { width, minWidth: width, maxWidth: width, left: leftOffset }
333
+ className: `dg-td${col.pinned === "left" ? " pinned-left" : ""} ${col.cellClassName || ""}`,
334
+ style: { width, minWidth: width, maxWidth: width, left: leftOffset, flex: col.flex }
298
335
  },
299
- col.render ? col.render(item[col.key], item) : String(item[col.key] ?? "")
336
+ (() => {
337
+ const field = String(col.key);
338
+ const rawValue = item[col.key];
339
+ let value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
340
+ const formattedValue = col.valueFormatter ? col.valueFormatter({ value, row: item, field }) : value;
341
+ if (col.renderCell) {
342
+ return col.renderCell({ value, row: item, field });
343
+ }
344
+ if (col.render) {
345
+ return col.render(value, item);
346
+ }
347
+ return String(formattedValue ?? "");
348
+ })()
300
349
  );
301
- }), actions && /* @__PURE__ */ import_react.default.createElement("td", { className: "dg-row-actions-cell" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-action-group" }, actions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
302
- "button",
303
- {
304
- key: i,
305
- className: "dg-row-action-btn",
306
- style: { color: action.color || "var(--text-secondary)" },
307
- onClick: () => action.onClick(item),
308
- title: action.label
309
- },
310
- action.icon
311
- )))))))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react.default.createElement(
350
+ }), actions && /* @__PURE__ */ import_react.default.createElement("td", { className: "dg-row-actions-cell" }, (() => {
351
+ const resolvedActions = typeof actions === "function" ? actions(item) : actions;
352
+ const visibleActions = resolvedActions.filter((a) => !a.show || a.show(item));
353
+ if (visibleActions.length === 0) return null;
354
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-action-group" }, visibleActions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
355
+ "button",
356
+ {
357
+ key: i,
358
+ className: "dg-row-action-btn",
359
+ style: { color: action.color || "var(--text-secondary)" },
360
+ onClick: () => action.onClick(item),
361
+ title: action.label
362
+ },
363
+ action.icon
364
+ ))));
365
+ })())))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react.default.createElement(
312
366
  "select",
313
367
  {
314
368
  value: pageSize,
@@ -349,7 +403,7 @@ function DataGrid({
349
403
  value: colSearch,
350
404
  onChange: (e) => setColSearch(e.target.value)
351
405
  }
352
- )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase())).map((col) => /* @__PURE__ */ import_react.default.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14 }) : /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: true }))) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ import_react.default.createElement("h3", null, "Filters"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.X, { size: 18 }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ import_react.default.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ import_react.default.createElement(
406
+ )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase()) && c.hideable !== false).map((col) => /* @__PURE__ */ import_react.default.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14 }) : /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => c.hideable !== false ? { ...c, hidden: true } : c)) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ import_react.default.createElement("h3", null, "Filters"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.X, { size: 18 }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ import_react.default.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ import_react.default.createElement(
353
407
  "button",
354
408
  {
355
409
  className: `dg-logic-btn${f.logic === "AND" ? " active" : ""}`,
@@ -3,6 +3,6 @@ import { DataGridProps } from './types.cjs';
3
3
 
4
4
  declare function DataGrid<T extends {
5
5
  id: string | number;
6
- }>({ columns: initialColumns, data, actions, pageSize: initialPageSize, pageSizeOptions, title }: DataGridProps<T>): React__default.JSX.Element;
6
+ }>({ columns: initialColumnsProp, data, actions, pageSize: initialPageSize, pageSizeOptions, title }: DataGridProps<T>): React__default.JSX.Element;
7
7
 
8
8
  export { DataGrid, DataGrid as default };
@@ -3,6 +3,6 @@ import { DataGridProps } from './types.js';
3
3
 
4
4
  declare function DataGrid<T extends {
5
5
  id: string | number;
6
- }>({ columns: initialColumns, data, actions, pageSize: initialPageSize, pageSizeOptions, title }: DataGridProps<T>): React__default.JSX.Element;
6
+ }>({ columns: initialColumnsProp, data, actions, pageSize: initialPageSize, pageSizeOptions, title }: DataGridProps<T>): React__default.JSX.Element;
7
7
 
8
8
  export { DataGrid, DataGrid as default };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DataGrid,
3
3
  DataGrid_default
4
- } from "../chunk-J6E3UO2W.js";
4
+ } from "../chunk-B6EOV25J.js";
5
5
  import "../chunk-LI4N7JWK.js";
6
6
  export {
7
7
  DataGrid,
@@ -37,18 +37,24 @@ module.exports = __toCommonJS(DataGrid_exports);
37
37
  var import_react = __toESM(require("react"), 1);
38
38
  var import_lucide_react = require("lucide-react");
39
39
  function DataGrid({
40
- columns: initialColumns,
40
+ columns: initialColumnsProp,
41
41
  data,
42
42
  actions,
43
43
  pageSize: initialPageSize = 10,
44
44
  pageSizeOptions = [5, 10, 25, 50],
45
45
  title
46
46
  }) {
47
+ const initialColumns = (0, import_react.useMemo)(() => initialColumnsProp.map((col) => ({
48
+ ...col,
49
+ key: col.key || col.field || "",
50
+ header: col.header || col.headerName || ""
51
+ })), [initialColumnsProp]);
47
52
  const [columns, setColumns] = (0, import_react.useState)(initialColumns);
48
53
  const [columnWidths, setColumnWidths] = (0, import_react.useState)(() => {
49
54
  const widths = {};
50
55
  initialColumns.forEach((col) => {
51
- widths[String(col.key)] = parseInt(col.width || "200");
56
+ const w = col.width || 200;
57
+ widths[String(col.key)] = typeof w === "number" ? w : parseInt(w);
52
58
  });
53
59
  return widths;
54
60
  });
@@ -67,16 +73,42 @@ function DataGrid({
67
73
  const [showManageColumns, setShowManageColumns] = (0, import_react.useState)(false);
68
74
  const [showAdvancedFilter, setShowAdvancedFilter] = (0, import_react.useState)(false);
69
75
  const [advancedFilters, setAdvancedFilters] = (0, import_react.useState)([
70
- { column: String(initialColumns[0].key), operator: "contains", value: "", logic: "AND" }
76
+ { column: String(initialColumns[0]?.key || ""), operator: "contains", value: "", logic: "AND" }
71
77
  ]);
78
+ (0, import_react.useEffect)(() => {
79
+ setColumns((prevColumns) => {
80
+ return initialColumns.map((newCol) => {
81
+ const prevCol = prevColumns.find((c) => (c.key || c.field) === (newCol.key || newCol.field));
82
+ if (!prevCol) return newCol;
83
+ return {
84
+ ...newCol,
85
+ hidden: prevCol.hidden !== void 0 ? prevCol.hidden : newCol.hidden,
86
+ pinned: prevCol.pinned !== void 0 ? prevCol.pinned : newCol.pinned
87
+ };
88
+ });
89
+ });
90
+ }, [initialColumns]);
91
+ (0, import_react.useEffect)(() => {
92
+ setColumnWidths((prev) => {
93
+ const next = { ...prev };
94
+ initialColumns.forEach((col) => {
95
+ const key = String(col.key);
96
+ if (next[key] === void 0) {
97
+ const w = col.width || 200;
98
+ next[key] = typeof w === "number" ? w : parseInt(w);
99
+ }
100
+ });
101
+ return next;
102
+ });
103
+ }, [initialColumns]);
72
104
  const [colSearch, setColSearch] = (0, import_react.useState)("");
73
105
  (0, import_react.useEffect)(() => {
74
106
  const handleMouseMove = (e) => {
75
107
  if (!resizingColumn) return;
76
108
  const col = columns.find((c) => String(c.key) === resizingColumn);
77
109
  const diff = e.clientX - startX;
78
- const minW = col?.minWidth ? parseInt(col.minWidth) : 80;
79
- const maxW = col?.maxWidth ? parseInt(col.maxWidth) : Infinity;
110
+ const minW = col?.minWidth ? typeof col.minWidth === "number" ? col.minWidth : parseInt(col.minWidth) : 80;
111
+ const maxW = col?.maxWidth ? typeof col.maxWidth === "number" ? col.maxWidth : parseInt(col.maxWidth) : Infinity;
80
112
  const newWidth = Math.min(maxW, Math.max(minW, startWidth + diff));
81
113
  setColumnWidths((prev) => ({ ...prev, [resizingColumn]: newWidth }));
82
114
  };
@@ -167,14 +199,19 @@ function DataGrid({
167
199
  }, [data, filterText, advancedFilters]);
168
200
  const sortedData = (0, import_react.useMemo)(() => {
169
201
  if (!sortKey || !sortDirection) return filteredData;
202
+ const col = columns.find((c) => c.key === sortKey);
170
203
  return [...filteredData].sort((a, b) => {
171
- const aVal = a[sortKey];
172
- const bVal = b[sortKey];
204
+ let aVal = a[sortKey];
205
+ let bVal = b[sortKey];
206
+ if (col?.valueGetter) {
207
+ aVal = col.valueGetter({ value: aVal, row: a, field: String(sortKey) });
208
+ bVal = col.valueGetter({ value: bVal, row: b, field: String(sortKey) });
209
+ }
173
210
  if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
174
211
  if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
175
212
  return 0;
176
213
  });
177
- }, [filteredData, sortKey, sortDirection]);
214
+ }, [filteredData, sortKey, sortDirection, columns]);
178
215
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
179
216
  const paginatedData = (0, import_react.useMemo)(() => {
180
217
  const start = (currentPage - 1) * pageSize;
@@ -254,8 +291,8 @@ function DataGrid({
254
291
  "th",
255
292
  {
256
293
  key: colKey,
257
- className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""}`,
258
- style: { width, minWidth: width, left: leftOffset }
294
+ className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""} ${col.headerClassName || ""}`,
295
+ style: { width, minWidth: width, left: leftOffset, flex: col.flex }
259
296
  },
260
297
  /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-th-inner" }, /* @__PURE__ */ import_react.default.createElement(
261
298
  "div",
@@ -266,7 +303,7 @@ function DataGrid({
266
303
  col.header,
267
304
  isSorted && sortDirection === "asc" && /* @__PURE__ */ import_react.default.createElement(import_lucide_react.ChevronUp, { size: 12 }),
268
305
  isSorted && sortDirection === "desc" && /* @__PURE__ */ import_react.default.createElement(import_lucide_react.ChevronDown, { size: 12 })
269
- ), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-th-actions" }, /* @__PURE__ */ import_react.default.createElement(
306
+ ), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-th-actions" }, !col.disableColumnMenu && /* @__PURE__ */ import_react.default.createElement(
270
307
  "button",
271
308
  {
272
309
  className: "dg-th-menu-btn",
@@ -294,22 +331,39 @@ function DataGrid({
294
331
  "td",
295
332
  {
296
333
  key: `${item.id}-${colKey}`,
297
- className: `dg-td${col.pinned === "left" ? " pinned-left" : ""}`,
298
- style: { width, minWidth: width, maxWidth: width, left: leftOffset }
334
+ className: `dg-td${col.pinned === "left" ? " pinned-left" : ""} ${col.cellClassName || ""}`,
335
+ style: { width, minWidth: width, maxWidth: width, left: leftOffset, flex: col.flex }
299
336
  },
300
- col.render ? col.render(item[col.key], item) : String(item[col.key] ?? "")
337
+ (() => {
338
+ const field = String(col.key);
339
+ const rawValue = item[col.key];
340
+ let value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
341
+ const formattedValue = col.valueFormatter ? col.valueFormatter({ value, row: item, field }) : value;
342
+ if (col.renderCell) {
343
+ return col.renderCell({ value, row: item, field });
344
+ }
345
+ if (col.render) {
346
+ return col.render(value, item);
347
+ }
348
+ return String(formattedValue ?? "");
349
+ })()
301
350
  );
302
- }), actions && /* @__PURE__ */ import_react.default.createElement("td", { className: "dg-row-actions-cell" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-action-group" }, actions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
303
- "button",
304
- {
305
- key: i,
306
- className: "dg-row-action-btn",
307
- style: { color: action.color || "var(--text-secondary)" },
308
- onClick: () => action.onClick(item),
309
- title: action.label
310
- },
311
- action.icon
312
- )))))))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react.default.createElement(
351
+ }), actions && /* @__PURE__ */ import_react.default.createElement("td", { className: "dg-row-actions-cell" }, (() => {
352
+ const resolvedActions = typeof actions === "function" ? actions(item) : actions;
353
+ const visibleActions = resolvedActions.filter((a) => !a.show || a.show(item));
354
+ if (visibleActions.length === 0) return null;
355
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-action-group" }, visibleActions.map((action, i) => /* @__PURE__ */ import_react.default.createElement(
356
+ "button",
357
+ {
358
+ key: i,
359
+ className: "dg-row-action-btn",
360
+ style: { color: action.color || "var(--text-secondary)" },
361
+ onClick: () => action.onClick(item),
362
+ title: action.label
363
+ },
364
+ action.icon
365
+ ))));
366
+ })())))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react.default.createElement(
313
367
  "select",
314
368
  {
315
369
  value: pageSize,
@@ -350,7 +404,7 @@ function DataGrid({
350
404
  value: colSearch,
351
405
  onChange: (e) => setColSearch(e.target.value)
352
406
  }
353
- )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase())).map((col) => /* @__PURE__ */ import_react.default.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14 }) : /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: true }))) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ import_react.default.createElement("h3", null, "Filters"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.X, { size: 18 }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ import_react.default.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ import_react.default.createElement(
407
+ )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase()) && c.hideable !== false).map((col) => /* @__PURE__ */ import_react.default.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14 }) : /* @__PURE__ */ import_react.default.createElement(import_lucide_react.EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => c.hideable !== false ? { ...c, hidden: true } : c)) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ import_react.default.createElement("h3", null, "Filters"), /* @__PURE__ */ import_react.default.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react.default.createElement(import_lucide_react.X, { size: 18 }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ import_react.default.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ import_react.default.createElement(
354
408
  "button",
355
409
  {
356
410
  className: `dg-logic-btn${f.logic === "AND" ? " active" : ""}`,
@@ -1,7 +1,7 @@
1
1
  import "../chunk-7KRG7VNW.js";
2
2
  import {
3
3
  DataGrid
4
- } from "../chunk-J6E3UO2W.js";
4
+ } from "../chunk-B6EOV25J.js";
5
5
  import "../chunk-LI4N7JWK.js";
6
6
  export {
7
7
  DataGrid
@@ -2,27 +2,52 @@ import React__default from 'react';
2
2
 
3
3
  type SortDirection = 'asc' | 'desc' | null;
4
4
  interface Column<T> {
5
- key: keyof T | string;
6
- header: string;
5
+ key?: keyof T | string;
6
+ field?: keyof T | string;
7
+ header?: string;
8
+ headerName?: string;
7
9
  sortable?: boolean;
8
10
  filterable?: boolean;
9
11
  render?: (value: any, item: T) => React__default.ReactNode;
10
- width?: string;
11
- minWidth?: string;
12
- maxWidth?: string;
12
+ renderCell?: (params: {
13
+ value: any;
14
+ row: T;
15
+ field: string;
16
+ }) => React__default.ReactNode;
17
+ valueGetter?: (params: {
18
+ value: any;
19
+ row: T;
20
+ field: string;
21
+ }) => any;
22
+ valueFormatter?: (params: {
23
+ value: any;
24
+ row: T;
25
+ field: string;
26
+ }) => string;
27
+ width?: string | number;
28
+ minWidth?: string | number;
29
+ maxWidth?: string | number;
30
+ flex?: number;
13
31
  pinned?: 'left' | 'right';
14
32
  hidden?: boolean;
33
+ type?: 'string' | 'number' | 'date' | 'boolean' | 'actions';
34
+ editable?: boolean;
35
+ headerClassName?: string;
36
+ cellClassName?: string;
37
+ hideable?: boolean;
38
+ disableColumnMenu?: boolean;
15
39
  }
16
40
  interface Action<T> {
17
41
  label: string;
18
42
  icon: React__default.ReactNode;
19
43
  onClick: (item: T) => void;
20
44
  color?: string;
45
+ show?: (item: T) => boolean;
21
46
  }
22
47
  interface DataGridProps<T> {
23
48
  columns: Column<T>[];
24
49
  data: T[];
25
- actions?: Action<T>[];
50
+ actions?: Action<T>[] | ((item: T) => Action<T>[]);
26
51
  pageSize?: number;
27
52
  pageSizeOptions?: number[];
28
53
  title?: string;
@@ -2,27 +2,52 @@ import React__default from 'react';
2
2
 
3
3
  type SortDirection = 'asc' | 'desc' | null;
4
4
  interface Column<T> {
5
- key: keyof T | string;
6
- header: string;
5
+ key?: keyof T | string;
6
+ field?: keyof T | string;
7
+ header?: string;
8
+ headerName?: string;
7
9
  sortable?: boolean;
8
10
  filterable?: boolean;
9
11
  render?: (value: any, item: T) => React__default.ReactNode;
10
- width?: string;
11
- minWidth?: string;
12
- maxWidth?: string;
12
+ renderCell?: (params: {
13
+ value: any;
14
+ row: T;
15
+ field: string;
16
+ }) => React__default.ReactNode;
17
+ valueGetter?: (params: {
18
+ value: any;
19
+ row: T;
20
+ field: string;
21
+ }) => any;
22
+ valueFormatter?: (params: {
23
+ value: any;
24
+ row: T;
25
+ field: string;
26
+ }) => string;
27
+ width?: string | number;
28
+ minWidth?: string | number;
29
+ maxWidth?: string | number;
30
+ flex?: number;
13
31
  pinned?: 'left' | 'right';
14
32
  hidden?: boolean;
33
+ type?: 'string' | 'number' | 'date' | 'boolean' | 'actions';
34
+ editable?: boolean;
35
+ headerClassName?: string;
36
+ cellClassName?: string;
37
+ hideable?: boolean;
38
+ disableColumnMenu?: boolean;
15
39
  }
16
40
  interface Action<T> {
17
41
  label: string;
18
42
  icon: React__default.ReactNode;
19
43
  onClick: (item: T) => void;
20
44
  color?: string;
45
+ show?: (item: T) => boolean;
21
46
  }
22
47
  interface DataGridProps<T> {
23
48
  columns: Column<T>[];
24
49
  data: T[];
25
- actions?: Action<T>[];
50
+ actions?: Action<T>[] | ((item: T) => Action<T>[]);
26
51
  pageSize?: number;
27
52
  pageSizeOptions?: number[];
28
53
  title?: string;
@@ -54,15 +54,15 @@ import "../chunk-XPJVVKOU.js";
54
54
  import "../chunk-GL43GPEM.js";
55
55
  import "../chunk-FZCFFVXW.js";
56
56
  import "../chunk-N26C33E6.js";
57
- import "../chunk-EB6MPFGC.js";
58
- import "../chunk-UPCMMCPQ.js";
59
57
  import "../chunk-AH6RCYDL.js";
60
58
  import "../chunk-3IBCGGN3.js";
61
59
  import "../chunk-MNPAE2ZF.js";
62
60
  import "../chunk-Q5XKCUE3.js";
61
+ import "../chunk-EB6MPFGC.js";
62
+ import "../chunk-UPCMMCPQ.js";
63
63
  import "../chunk-X357WQOT.js";
64
64
  import "../chunk-7KRG7VNW.js";
65
- import "../chunk-J6E3UO2W.js";
65
+ import "../chunk-B6EOV25J.js";
66
66
  import "../chunk-GJGRMMAQ.js";
67
67
  import "../chunk-66HHM7VI.js";
68
68
  import "../chunk-QPGJCRBS.js";
@@ -54,15 +54,15 @@ import "../chunk-XPJVVKOU.js";
54
54
  import "../chunk-GL43GPEM.js";
55
55
  import "../chunk-FZCFFVXW.js";
56
56
  import "../chunk-N26C33E6.js";
57
- import "../chunk-EB6MPFGC.js";
58
- import "../chunk-UPCMMCPQ.js";
59
57
  import "../chunk-AH6RCYDL.js";
60
58
  import "../chunk-3IBCGGN3.js";
61
59
  import "../chunk-MNPAE2ZF.js";
62
60
  import "../chunk-Q5XKCUE3.js";
61
+ import "../chunk-EB6MPFGC.js";
62
+ import "../chunk-UPCMMCPQ.js";
63
63
  import "../chunk-X357WQOT.js";
64
64
  import "../chunk-7KRG7VNW.js";
65
- import "../chunk-J6E3UO2W.js";
65
+ import "../chunk-B6EOV25J.js";
66
66
  import "../chunk-GJGRMMAQ.js";
67
67
  import "../chunk-66HHM7VI.js";
68
68
  import "../chunk-QPGJCRBS.js";
@@ -19,18 +19,24 @@ import {
19
19
  Plus
20
20
  } from "lucide-react";
21
21
  function DataGrid({
22
- columns: initialColumns,
22
+ columns: initialColumnsProp,
23
23
  data,
24
24
  actions,
25
25
  pageSize: initialPageSize = 10,
26
26
  pageSizeOptions = [5, 10, 25, 50],
27
27
  title
28
28
  }) {
29
+ const initialColumns = useMemo(() => initialColumnsProp.map((col) => ({
30
+ ...col,
31
+ key: col.key || col.field || "",
32
+ header: col.header || col.headerName || ""
33
+ })), [initialColumnsProp]);
29
34
  const [columns, setColumns] = useState(initialColumns);
30
35
  const [columnWidths, setColumnWidths] = useState(() => {
31
36
  const widths = {};
32
37
  initialColumns.forEach((col) => {
33
- widths[String(col.key)] = parseInt(col.width || "200");
38
+ const w = col.width || 200;
39
+ widths[String(col.key)] = typeof w === "number" ? w : parseInt(w);
34
40
  });
35
41
  return widths;
36
42
  });
@@ -49,16 +55,42 @@ function DataGrid({
49
55
  const [showManageColumns, setShowManageColumns] = useState(false);
50
56
  const [showAdvancedFilter, setShowAdvancedFilter] = useState(false);
51
57
  const [advancedFilters, setAdvancedFilters] = useState([
52
- { column: String(initialColumns[0].key), operator: "contains", value: "", logic: "AND" }
58
+ { column: String(initialColumns[0]?.key || ""), operator: "contains", value: "", logic: "AND" }
53
59
  ]);
60
+ useEffect(() => {
61
+ setColumns((prevColumns) => {
62
+ return initialColumns.map((newCol) => {
63
+ const prevCol = prevColumns.find((c) => (c.key || c.field) === (newCol.key || newCol.field));
64
+ if (!prevCol) return newCol;
65
+ return {
66
+ ...newCol,
67
+ hidden: prevCol.hidden !== void 0 ? prevCol.hidden : newCol.hidden,
68
+ pinned: prevCol.pinned !== void 0 ? prevCol.pinned : newCol.pinned
69
+ };
70
+ });
71
+ });
72
+ }, [initialColumns]);
73
+ useEffect(() => {
74
+ setColumnWidths((prev) => {
75
+ const next = { ...prev };
76
+ initialColumns.forEach((col) => {
77
+ const key = String(col.key);
78
+ if (next[key] === void 0) {
79
+ const w = col.width || 200;
80
+ next[key] = typeof w === "number" ? w : parseInt(w);
81
+ }
82
+ });
83
+ return next;
84
+ });
85
+ }, [initialColumns]);
54
86
  const [colSearch, setColSearch] = useState("");
55
87
  useEffect(() => {
56
88
  const handleMouseMove = (e) => {
57
89
  if (!resizingColumn) return;
58
90
  const col = columns.find((c) => String(c.key) === resizingColumn);
59
91
  const diff = e.clientX - startX;
60
- const minW = col?.minWidth ? parseInt(col.minWidth) : 80;
61
- const maxW = col?.maxWidth ? parseInt(col.maxWidth) : Infinity;
92
+ const minW = col?.minWidth ? typeof col.minWidth === "number" ? col.minWidth : parseInt(col.minWidth) : 80;
93
+ const maxW = col?.maxWidth ? typeof col.maxWidth === "number" ? col.maxWidth : parseInt(col.maxWidth) : Infinity;
62
94
  const newWidth = Math.min(maxW, Math.max(minW, startWidth + diff));
63
95
  setColumnWidths((prev) => ({ ...prev, [resizingColumn]: newWidth }));
64
96
  };
@@ -149,14 +181,19 @@ function DataGrid({
149
181
  }, [data, filterText, advancedFilters]);
150
182
  const sortedData = useMemo(() => {
151
183
  if (!sortKey || !sortDirection) return filteredData;
184
+ const col = columns.find((c) => c.key === sortKey);
152
185
  return [...filteredData].sort((a, b) => {
153
- const aVal = a[sortKey];
154
- const bVal = b[sortKey];
186
+ let aVal = a[sortKey];
187
+ let bVal = b[sortKey];
188
+ if (col?.valueGetter) {
189
+ aVal = col.valueGetter({ value: aVal, row: a, field: String(sortKey) });
190
+ bVal = col.valueGetter({ value: bVal, row: b, field: String(sortKey) });
191
+ }
155
192
  if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
156
193
  if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
157
194
  return 0;
158
195
  });
159
- }, [filteredData, sortKey, sortDirection]);
196
+ }, [filteredData, sortKey, sortDirection, columns]);
160
197
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
161
198
  const paginatedData = useMemo(() => {
162
199
  const start = (currentPage - 1) * pageSize;
@@ -236,8 +273,8 @@ function DataGrid({
236
273
  "th",
237
274
  {
238
275
  key: colKey,
239
- className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""}`,
240
- style: { width, minWidth: width, left: leftOffset }
276
+ className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""} ${col.headerClassName || ""}`,
277
+ style: { width, minWidth: width, left: leftOffset, flex: col.flex }
241
278
  },
242
279
  /* @__PURE__ */ React.createElement("div", { className: "dg-th-inner" }, /* @__PURE__ */ React.createElement(
243
280
  "div",
@@ -248,7 +285,7 @@ function DataGrid({
248
285
  col.header,
249
286
  isSorted && sortDirection === "asc" && /* @__PURE__ */ React.createElement(ChevronUp, { size: 12 }),
250
287
  isSorted && sortDirection === "desc" && /* @__PURE__ */ React.createElement(ChevronDown, { size: 12 })
251
- ), /* @__PURE__ */ React.createElement("div", { className: "dg-th-actions" }, /* @__PURE__ */ React.createElement(
288
+ ), /* @__PURE__ */ React.createElement("div", { className: "dg-th-actions" }, !col.disableColumnMenu && /* @__PURE__ */ React.createElement(
252
289
  "button",
253
290
  {
254
291
  className: "dg-th-menu-btn",
@@ -276,22 +313,39 @@ function DataGrid({
276
313
  "td",
277
314
  {
278
315
  key: `${item.id}-${colKey}`,
279
- className: `dg-td${col.pinned === "left" ? " pinned-left" : ""}`,
280
- style: { width, minWidth: width, maxWidth: width, left: leftOffset }
316
+ className: `dg-td${col.pinned === "left" ? " pinned-left" : ""} ${col.cellClassName || ""}`,
317
+ style: { width, minWidth: width, maxWidth: width, left: leftOffset, flex: col.flex }
281
318
  },
282
- col.render ? col.render(item[col.key], item) : String(item[col.key] ?? "")
319
+ (() => {
320
+ const field = String(col.key);
321
+ const rawValue = item[col.key];
322
+ let value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
323
+ const formattedValue = col.valueFormatter ? col.valueFormatter({ value, row: item, field }) : value;
324
+ if (col.renderCell) {
325
+ return col.renderCell({ value, row: item, field });
326
+ }
327
+ if (col.render) {
328
+ return col.render(value, item);
329
+ }
330
+ return String(formattedValue ?? "");
331
+ })()
283
332
  );
284
- }), actions && /* @__PURE__ */ React.createElement("td", { className: "dg-row-actions-cell" }, /* @__PURE__ */ React.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ React.createElement("div", { className: "dg-action-group" }, actions.map((action, i) => /* @__PURE__ */ React.createElement(
285
- "button",
286
- {
287
- key: i,
288
- className: "dg-row-action-btn",
289
- style: { color: action.color || "var(--text-secondary)" },
290
- onClick: () => action.onClick(item),
291
- title: action.label
292
- },
293
- action.icon
294
- )))))))))), /* @__PURE__ */ React.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ React.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ React.createElement("span", null, "Rows per page:"), /* @__PURE__ */ React.createElement(
333
+ }), actions && /* @__PURE__ */ React.createElement("td", { className: "dg-row-actions-cell" }, (() => {
334
+ const resolvedActions = typeof actions === "function" ? actions(item) : actions;
335
+ const visibleActions = resolvedActions.filter((a) => !a.show || a.show(item));
336
+ if (visibleActions.length === 0) return null;
337
+ return /* @__PURE__ */ React.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ React.createElement("div", { className: "dg-action-group" }, visibleActions.map((action, i) => /* @__PURE__ */ React.createElement(
338
+ "button",
339
+ {
340
+ key: i,
341
+ className: "dg-row-action-btn",
342
+ style: { color: action.color || "var(--text-secondary)" },
343
+ onClick: () => action.onClick(item),
344
+ title: action.label
345
+ },
346
+ action.icon
347
+ ))));
348
+ })())))))), /* @__PURE__ */ React.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ React.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ React.createElement("span", null, "Rows per page:"), /* @__PURE__ */ React.createElement(
295
349
  "select",
296
350
  {
297
351
  value: pageSize,
@@ -332,7 +386,7 @@ function DataGrid({
332
386
  value: colSearch,
333
387
  onChange: (e) => setColSearch(e.target.value)
334
388
  }
335
- )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase())).map((col) => /* @__PURE__ */ React.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ React.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ React.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ React.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ React.createElement(EyeOff, { size: 14 }) : /* @__PURE__ */ React.createElement(EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ React.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ React.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ React.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: true }))) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ React.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ React.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ React.createElement("h3", null, "Filters"), /* @__PURE__ */ React.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ React.createElement(X, { size: 18 }))), /* @__PURE__ */ React.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ React.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ React.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ React.createElement(
389
+ )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase()) && c.hideable !== false).map((col) => /* @__PURE__ */ React.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ React.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ React.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ React.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ React.createElement(EyeOff, { size: 14 }) : /* @__PURE__ */ React.createElement(EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ React.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ React.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ React.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => c.hideable !== false ? { ...c, hidden: true } : c)) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ React.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ React.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ React.createElement("h3", null, "Filters"), /* @__PURE__ */ React.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ React.createElement(X, { size: 18 }))), /* @__PURE__ */ React.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ React.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ React.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ React.createElement(
336
390
  "button",
337
391
  {
338
392
  className: `dg-logic-btn${f.logic === "AND" ? " active" : ""}`,
package/dist/main.cjs CHANGED
@@ -30377,18 +30377,24 @@ var AddressLookup_default = AddressLookup;
30377
30377
  var import_react18 = __toESM(require("react"), 1);
30378
30378
  var import_lucide_react = require("lucide-react");
30379
30379
  function DataGrid({
30380
- columns: initialColumns,
30380
+ columns: initialColumnsProp,
30381
30381
  data,
30382
30382
  actions,
30383
30383
  pageSize: initialPageSize = 10,
30384
30384
  pageSizeOptions = [5, 10, 25, 50],
30385
30385
  title
30386
30386
  }) {
30387
+ const initialColumns = (0, import_react18.useMemo)(() => initialColumnsProp.map((col) => ({
30388
+ ...col,
30389
+ key: col.key || col.field || "",
30390
+ header: col.header || col.headerName || ""
30391
+ })), [initialColumnsProp]);
30387
30392
  const [columns, setColumns] = (0, import_react18.useState)(initialColumns);
30388
30393
  const [columnWidths, setColumnWidths] = (0, import_react18.useState)(() => {
30389
30394
  const widths = {};
30390
30395
  initialColumns.forEach((col) => {
30391
- widths[String(col.key)] = parseInt(col.width || "200");
30396
+ const w = col.width || 200;
30397
+ widths[String(col.key)] = typeof w === "number" ? w : parseInt(w);
30392
30398
  });
30393
30399
  return widths;
30394
30400
  });
@@ -30407,16 +30413,42 @@ function DataGrid({
30407
30413
  const [showManageColumns, setShowManageColumns] = (0, import_react18.useState)(false);
30408
30414
  const [showAdvancedFilter, setShowAdvancedFilter] = (0, import_react18.useState)(false);
30409
30415
  const [advancedFilters, setAdvancedFilters] = (0, import_react18.useState)([
30410
- { column: String(initialColumns[0].key), operator: "contains", value: "", logic: "AND" }
30416
+ { column: String(initialColumns[0]?.key || ""), operator: "contains", value: "", logic: "AND" }
30411
30417
  ]);
30418
+ (0, import_react18.useEffect)(() => {
30419
+ setColumns((prevColumns) => {
30420
+ return initialColumns.map((newCol) => {
30421
+ const prevCol = prevColumns.find((c) => (c.key || c.field) === (newCol.key || newCol.field));
30422
+ if (!prevCol) return newCol;
30423
+ return {
30424
+ ...newCol,
30425
+ hidden: prevCol.hidden !== void 0 ? prevCol.hidden : newCol.hidden,
30426
+ pinned: prevCol.pinned !== void 0 ? prevCol.pinned : newCol.pinned
30427
+ };
30428
+ });
30429
+ });
30430
+ }, [initialColumns]);
30431
+ (0, import_react18.useEffect)(() => {
30432
+ setColumnWidths((prev) => {
30433
+ const next = { ...prev };
30434
+ initialColumns.forEach((col) => {
30435
+ const key = String(col.key);
30436
+ if (next[key] === void 0) {
30437
+ const w = col.width || 200;
30438
+ next[key] = typeof w === "number" ? w : parseInt(w);
30439
+ }
30440
+ });
30441
+ return next;
30442
+ });
30443
+ }, [initialColumns]);
30412
30444
  const [colSearch, setColSearch] = (0, import_react18.useState)("");
30413
30445
  (0, import_react18.useEffect)(() => {
30414
30446
  const handleMouseMove = (e) => {
30415
30447
  if (!resizingColumn) return;
30416
30448
  const col = columns.find((c) => String(c.key) === resizingColumn);
30417
30449
  const diff = e.clientX - startX;
30418
- const minW = col?.minWidth ? parseInt(col.minWidth) : 80;
30419
- const maxW = col?.maxWidth ? parseInt(col.maxWidth) : Infinity;
30450
+ const minW = col?.minWidth ? typeof col.minWidth === "number" ? col.minWidth : parseInt(col.minWidth) : 80;
30451
+ const maxW = col?.maxWidth ? typeof col.maxWidth === "number" ? col.maxWidth : parseInt(col.maxWidth) : Infinity;
30420
30452
  const newWidth = Math.min(maxW, Math.max(minW, startWidth + diff));
30421
30453
  setColumnWidths((prev) => ({ ...prev, [resizingColumn]: newWidth }));
30422
30454
  };
@@ -30507,14 +30539,19 @@ function DataGrid({
30507
30539
  }, [data, filterText, advancedFilters]);
30508
30540
  const sortedData = (0, import_react18.useMemo)(() => {
30509
30541
  if (!sortKey || !sortDirection) return filteredData;
30542
+ const col = columns.find((c) => c.key === sortKey);
30510
30543
  return [...filteredData].sort((a, b) => {
30511
- const aVal = a[sortKey];
30512
- const bVal = b[sortKey];
30544
+ let aVal = a[sortKey];
30545
+ let bVal = b[sortKey];
30546
+ if (col?.valueGetter) {
30547
+ aVal = col.valueGetter({ value: aVal, row: a, field: String(sortKey) });
30548
+ bVal = col.valueGetter({ value: bVal, row: b, field: String(sortKey) });
30549
+ }
30513
30550
  if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
30514
30551
  if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
30515
30552
  return 0;
30516
30553
  });
30517
- }, [filteredData, sortKey, sortDirection]);
30554
+ }, [filteredData, sortKey, sortDirection, columns]);
30518
30555
  const totalPages = Math.max(1, Math.ceil(sortedData.length / pageSize));
30519
30556
  const paginatedData = (0, import_react18.useMemo)(() => {
30520
30557
  const start = (currentPage - 1) * pageSize;
@@ -30594,8 +30631,8 @@ function DataGrid({
30594
30631
  "th",
30595
30632
  {
30596
30633
  key: colKey,
30597
- className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""}`,
30598
- style: { width, minWidth: width, left: leftOffset }
30634
+ className: `dg-thead-cell${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""} ${col.headerClassName || ""}`,
30635
+ style: { width, minWidth: width, left: leftOffset, flex: col.flex }
30599
30636
  },
30600
30637
  /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-th-inner" }, /* @__PURE__ */ import_react18.default.createElement(
30601
30638
  "div",
@@ -30606,7 +30643,7 @@ function DataGrid({
30606
30643
  col.header,
30607
30644
  isSorted && sortDirection === "asc" && /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.ChevronUp, { size: 12 }),
30608
30645
  isSorted && sortDirection === "desc" && /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.ChevronDown, { size: 12 })
30609
- ), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-th-actions" }, /* @__PURE__ */ import_react18.default.createElement(
30646
+ ), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-th-actions" }, !col.disableColumnMenu && /* @__PURE__ */ import_react18.default.createElement(
30610
30647
  "button",
30611
30648
  {
30612
30649
  className: "dg-th-menu-btn",
@@ -30634,22 +30671,39 @@ function DataGrid({
30634
30671
  "td",
30635
30672
  {
30636
30673
  key: `${item.id}-${colKey}`,
30637
- className: `dg-td${col.pinned === "left" ? " pinned-left" : ""}`,
30638
- style: { width, minWidth: width, maxWidth: width, left: leftOffset }
30674
+ className: `dg-td${col.pinned === "left" ? " pinned-left" : ""} ${col.cellClassName || ""}`,
30675
+ style: { width, minWidth: width, maxWidth: width, left: leftOffset, flex: col.flex }
30639
30676
  },
30640
- col.render ? col.render(item[col.key], item) : String(item[col.key] ?? "")
30677
+ (() => {
30678
+ const field = String(col.key);
30679
+ const rawValue = item[col.key];
30680
+ let value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
30681
+ const formattedValue = col.valueFormatter ? col.valueFormatter({ value, row: item, field }) : value;
30682
+ if (col.renderCell) {
30683
+ return col.renderCell({ value, row: item, field });
30684
+ }
30685
+ if (col.render) {
30686
+ return col.render(value, item);
30687
+ }
30688
+ return String(formattedValue ?? "");
30689
+ })()
30641
30690
  );
30642
- }), actions && /* @__PURE__ */ import_react18.default.createElement("td", { className: "dg-row-actions-cell" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-action-group" }, actions.map((action, i) => /* @__PURE__ */ import_react18.default.createElement(
30643
- "button",
30644
- {
30645
- key: i,
30646
- className: "dg-row-action-btn",
30647
- style: { color: action.color || "var(--text-secondary)" },
30648
- onClick: () => action.onClick(item),
30649
- title: action.label
30650
- },
30651
- action.icon
30652
- )))))))))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react18.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react18.default.createElement(
30691
+ }), actions && /* @__PURE__ */ import_react18.default.createElement("td", { className: "dg-row-actions-cell" }, (() => {
30692
+ const resolvedActions = typeof actions === "function" ? actions(item) : actions;
30693
+ const visibleActions = resolvedActions.filter((a) => !a.show || a.show(item));
30694
+ if (visibleActions.length === 0) return null;
30695
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-row-actions" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-action-group" }, visibleActions.map((action, i) => /* @__PURE__ */ import_react18.default.createElement(
30696
+ "button",
30697
+ {
30698
+ key: i,
30699
+ className: "dg-row-action-btn",
30700
+ style: { color: action.color || "var(--text-secondary)" },
30701
+ onClick: () => action.onClick(item),
30702
+ title: action.label
30703
+ },
30704
+ action.icon
30705
+ ))));
30706
+ })())))))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react18.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react18.default.createElement(
30653
30707
  "select",
30654
30708
  {
30655
30709
  value: pageSize,
@@ -30690,7 +30744,7 @@ function DataGrid({
30690
30744
  value: colSearch,
30691
30745
  onChange: (e) => setColSearch(e.target.value)
30692
30746
  }
30693
- )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase())).map((col) => /* @__PURE__ */ import_react18.default.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.EyeOff, { size: 14 }) : /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: true }))) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ import_react18.default.createElement("h3", null, "Filters"), /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.X, { size: 18 }))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ import_react18.default.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ import_react18.default.createElement(
30747
+ )), columns.filter((c) => c.header.toLowerCase().includes(colSearch.toLowerCase()) && c.hideable !== false).map((col) => /* @__PURE__ */ import_react18.default.createElement("div", { key: String(col.key), className: "dg-col-row" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-col-label" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-col-dot", style: { background: col.hidden ? "var(--border-color)" : "var(--primary-color)" } }), col.header), /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-icon-btn", onClick: () => toggleHide(String(col.key)) }, col.hidden ? /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.EyeOff, { size: 14 }) : /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.EyeOff, { size: 14, style: { opacity: 0.4 } }))))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-footer" }, /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => ({ ...c, hidden: false }))) }, "Show All"), /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-action-btn", onClick: () => setColumns((p) => p.map((c) => c.hideable !== false ? { ...c, hidden: true } : c)) }, "Hide All")))), showAdvancedFilter && /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-overlay", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal dg-modal-wide", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-header" }, /* @__PURE__ */ import_react18.default.createElement("h3", null, "Filters"), /* @__PURE__ */ import_react18.default.createElement("button", { className: "dg-icon-btn", onClick: () => setShowAdvancedFilter(false) }, /* @__PURE__ */ import_react18.default.createElement(import_lucide_react.X, { size: 18 }))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-modal-body" }, advancedFilters.map((f, idx) => /* @__PURE__ */ import_react18.default.createElement("div", { key: idx }, idx > 0 && /* @__PURE__ */ import_react18.default.createElement("div", { className: "dg-filter-logic" }, /* @__PURE__ */ import_react18.default.createElement(
30694
30748
  "button",
30695
30749
  {
30696
30750
  className: `dg-logic-btn${f.logic === "AND" ? " active" : ""}`,
package/dist/main.js CHANGED
@@ -158,12 +158,6 @@ import {
158
158
  import {
159
159
  downloadPdfIcon_default
160
160
  } from "./chunk-N26C33E6.js";
161
- import {
162
- AddressLookup_default
163
- } from "./chunk-EB6MPFGC.js";
164
- import {
165
- FloatingInput
166
- } from "./chunk-UPCMMCPQ.js";
167
161
  import {
168
162
  activateUserIcon_default
169
163
  } from "./chunk-AH6RCYDL.js";
@@ -176,13 +170,19 @@ import {
176
170
  import {
177
171
  closeIcon_default
178
172
  } from "./chunk-Q5XKCUE3.js";
173
+ import {
174
+ AddressLookup_default
175
+ } from "./chunk-EB6MPFGC.js";
176
+ import {
177
+ FloatingInput
178
+ } from "./chunk-UPCMMCPQ.js";
179
179
  import {
180
180
  Checkbox
181
181
  } from "./chunk-X357WQOT.js";
182
182
  import "./chunk-7KRG7VNW.js";
183
183
  import {
184
184
  DataGrid
185
- } from "./chunk-J6E3UO2W.js";
185
+ } from "./chunk-B6EOV25J.js";
186
186
  import "./chunk-GJGRMMAQ.js";
187
187
  import {
188
188
  RichTextEditor
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.1.66",
4
+ "version": "0.1.68",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/style.css",
@@ -67,4 +67,4 @@
67
67
  "react": "^18.0.0 || ^19.0.0",
68
68
  "react-dom": "^18.0.0 || ^19.0.0"
69
69
  }
70
- }
70
+ }