@vesture/react 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ButtonHTMLAttributes, LabelHTMLAttributes, InputHTMLAttributes, TextareaHTMLAttributes, SelectHTMLAttributes, ReactNode, HTMLAttributes, ReactElement, MutableRefObject, AnchorHTMLAttributes, ForwardedRef, CSSProperties } from 'react';
2
+ import { ButtonHTMLAttributes, LabelHTMLAttributes, InputHTMLAttributes, TextareaHTMLAttributes, SelectHTMLAttributes, ReactNode, HTMLAttributes, ReactElement, MutableRefObject, AnchorHTMLAttributes, MouseEvent, ForwardedRef, CSSProperties } from 'react';
3
3
  import { Placement } from '@floating-ui/react';
4
4
  export { defaultThemeClass, defaultThemeVars, vars } from '@vesture/tokens';
5
5
 
@@ -278,6 +278,7 @@ interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
278
278
  }
279
279
  declare function Alert({ variant, title: titleText, onDismiss, children, className, ...rest }: AlertProps): ReactElement;
280
280
 
281
+ type AggregateType = "sum" | "avg" | "count" | "min" | "max";
281
282
  interface DataGridColumn<T> {
282
283
  key: string;
283
284
  header: ReactNode;
@@ -297,6 +298,8 @@ interface DataGridColumn<T> {
297
298
  render?: (row: T) => ReactNode;
298
299
  /** Value used when exporting this column; falls back to accessor, then the raw row value. Not derived from render, since render returns a ReactNode. */
299
300
  exportValue?: (row: T) => string | number;
301
+ /** Computed per-group when the grid is grouped, and shown in this column's slot within the group header row. */
302
+ aggregate?: AggregateType | ((rows: T[]) => ReactNode);
300
303
  }
301
304
  type SortDirection = "asc" | "desc" | null;
302
305
  interface SortState {
@@ -336,6 +339,19 @@ interface DataGridProps<T> {
336
339
  onPageChange?: (page: number) => void;
337
340
  /** Renders an "Export" button in the grid's toolbar that exports the current view to Excel. */
338
341
  enableExport?: boolean;
342
+ /** Groups rows by this column key (single-level). undefined = no grouping. */
343
+ groupBy?: string;
344
+ defaultGroupBy?: string;
345
+ onGroupByChange?: (key: string | undefined) => void;
346
+ /** Which group keys are expanded. Default: all expanded. */
347
+ expandedGroups?: Set<string>;
348
+ defaultExpandedGroups?: Set<string>;
349
+ onExpandedGroupsChange?: (expanded: Set<string>) => void;
350
+ /** Height of a group header row; defaults to rowHeight. */
351
+ groupRowHeight?: number;
352
+ onRowClick?: (row: T, event: MouseEvent<HTMLDivElement>) => void;
353
+ onRowDoubleClick?: (row: T, event: MouseEvent<HTMLDivElement>) => void;
354
+ onCellClick?: (row: T, column: DataGridColumn<T>, event: MouseEvent<HTMLDivElement>) => void;
339
355
  }
340
356
  interface DataGridExportOptions {
341
357
  filename?: string;
package/dist/index.js CHANGED
@@ -1116,12 +1116,18 @@ var body3 = "DataGrid_body__7xivx77";
1116
1116
  var cell = "DataGrid_cell__7xivx79";
1117
1117
  var checkboxCell = "DataGrid_checkboxCell__7xivx7a";
1118
1118
  var container = "DataGrid_container__7xivx71";
1119
- var editInput = "DataGrid_editInput__7xivx7p";
1119
+ var editInput = "DataGrid_editInput__7xivx7v";
1120
1120
  var emptyState = "DataGrid_emptyState__7xivx7b";
1121
1121
  var filterCell = "DataGrid_filterCell__7xivx7j";
1122
1122
  var filterInput = "DataGrid_filterInput__7xivx7k";
1123
1123
  var filterRow = "DataGrid_filterRow__7xivx7i";
1124
1124
  var gridWrapper = "DataGrid_gridWrapper__7xivx7m";
1125
+ var groupAggregateItem = "DataGrid_groupAggregateItem__7xivx7u";
1126
+ var groupAggregates = "DataGrid_groupAggregates__7xivx7t";
1127
+ var groupChevron = "DataGrid_groupChevron__7xivx7q";
1128
+ var groupCount = "DataGrid_groupCount__7xivx7s";
1129
+ var groupLabel = "DataGrid_groupLabel__7xivx7r";
1130
+ var groupRow = "DataGrid_groupRow__7xivx7p";
1125
1131
  var headerButton = "DataGrid_headerButton__7xivx74";
1126
1132
  var headerCell = "DataGrid_headerCell__7xivx73";
1127
1133
  var headerRow = "DataGrid_headerRow__7xivx72";
@@ -1206,6 +1212,40 @@ function getCellValue(column, row3) {
1206
1212
  }
1207
1213
  return row3[column.key];
1208
1214
  }
1215
+ function getGroupValue(row3, groupBy, column) {
1216
+ if (column) {
1217
+ return String(getCellValue(column, row3) ?? "");
1218
+ }
1219
+ return String(row3[groupBy] ?? "");
1220
+ }
1221
+ function formatAggregateNumber(value) {
1222
+ return Number.isInteger(value) ? value : value.toFixed(2);
1223
+ }
1224
+ function computeAggregate(column, rows) {
1225
+ const { aggregate } = column;
1226
+ if (!aggregate) return null;
1227
+ if (typeof aggregate === "function") {
1228
+ return aggregate(rows);
1229
+ }
1230
+ if (aggregate === "count") {
1231
+ return rows.length;
1232
+ }
1233
+ const values = rows.map((r) => Number(getCellValue(column, r)) || 0);
1234
+ switch (aggregate) {
1235
+ case "sum":
1236
+ return formatAggregateNumber(values.reduce((a, b) => a + b, 0));
1237
+ case "avg":
1238
+ return formatAggregateNumber(
1239
+ values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0
1240
+ );
1241
+ case "min":
1242
+ return formatAggregateNumber(values.length ? Math.min(...values) : 0);
1243
+ case "max":
1244
+ return formatAggregateNumber(values.length ? Math.max(...values) : 0);
1245
+ default:
1246
+ return null;
1247
+ }
1248
+ }
1209
1249
  function DataGridInner({
1210
1250
  columns,
1211
1251
  data,
@@ -1228,7 +1268,17 @@ function DataGridInner({
1228
1268
  page,
1229
1269
  pageSize,
1230
1270
  onPageChange,
1231
- enableExport = false
1271
+ enableExport = false,
1272
+ groupBy: controlledGroupBy,
1273
+ defaultGroupBy,
1274
+ onGroupByChange,
1275
+ expandedGroups: controlledExpandedGroups,
1276
+ defaultExpandedGroups,
1277
+ onExpandedGroupsChange,
1278
+ groupRowHeight,
1279
+ onRowClick,
1280
+ onRowDoubleClick,
1281
+ onCellClick
1232
1282
  }, ref) {
1233
1283
  const [uncontrolledSort, setUncontrolledSort] = useState8(
1234
1284
  null
@@ -1245,10 +1295,31 @@ function DataGridInner({
1245
1295
  const [scrollTop, setScrollTop] = useState8(0);
1246
1296
  const [editingRowId, setEditingRowId] = useState8(null);
1247
1297
  const [editDraft, setEditDraft] = useState8({});
1298
+ const [uncontrolledGroupBy, setUncontrolledGroupBy] = useState8(defaultGroupBy);
1299
+ const [uncontrolledExpandedGroups, setUncontrolledExpandedGroups] = useState8(defaultExpandedGroups);
1248
1300
  const editingEnabled = Boolean(onRowEdit);
1249
1301
  const sort = controlledSort !== void 0 ? controlledSort : uncontrolledSort;
1250
1302
  const filters = controlledFilters !== void 0 ? controlledFilters : uncontrolledFilters;
1251
1303
  const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;
1304
+ const groupBy = controlledGroupBy !== void 0 ? controlledGroupBy : uncontrolledGroupBy;
1305
+ const expandedGroups = controlledExpandedGroups !== void 0 ? controlledExpandedGroups : uncontrolledExpandedGroups;
1306
+ const setExpandedGroups = (next) => {
1307
+ if (controlledExpandedGroups === void 0) {
1308
+ setUncontrolledExpandedGroups(next);
1309
+ }
1310
+ onExpandedGroupsChange?.(next);
1311
+ };
1312
+ const isGroupExpanded = (key) => expandedGroups === void 0 ? true : expandedGroups.has(key);
1313
+ const toggleGroupExpanded = (key, allGroupKeys) => {
1314
+ const current2 = expandedGroups ?? new Set(allGroupKeys);
1315
+ const next = new Set(current2);
1316
+ if (next.has(key)) {
1317
+ next.delete(key);
1318
+ } else {
1319
+ next.add(key);
1320
+ }
1321
+ setExpandedGroups(next);
1322
+ };
1252
1323
  const setSort = (next) => {
1253
1324
  if (controlledSort === void 0) {
1254
1325
  setUncontrolledSort(next);
@@ -1312,6 +1383,44 @@ function DataGridInner({
1312
1383
  );
1313
1384
  return sort.direction === "desc" ? sorted.reverse() : sorted;
1314
1385
  }, [filteredData, sort, columns, serverSide, data]);
1386
+ const groupColumn = useMemo2(
1387
+ () => groupBy ? columns.find((c) => c.key === groupBy) : void 0,
1388
+ [columns, groupBy]
1389
+ );
1390
+ const groupedMap = useMemo2(() => {
1391
+ if (!groupBy) return null;
1392
+ const map = /* @__PURE__ */ new Map();
1393
+ for (const row3 of sortedData) {
1394
+ const key = getGroupValue(row3, groupBy, groupColumn);
1395
+ const existing = map.get(key);
1396
+ if (existing) {
1397
+ existing.push(row3);
1398
+ } else {
1399
+ map.set(key, [row3]);
1400
+ }
1401
+ }
1402
+ return map;
1403
+ }, [sortedData, groupBy, groupColumn]);
1404
+ const flatEntries = useMemo2(() => {
1405
+ if (!groupedMap) {
1406
+ return sortedData.map((row3) => ({ type: "data", row: row3 }));
1407
+ }
1408
+ const entries = [];
1409
+ for (const [key, rows] of groupedMap) {
1410
+ const collapsed = !isGroupExpanded(key);
1411
+ entries.push({ type: "group", key, rows, collapsed });
1412
+ if (!collapsed) {
1413
+ for (const row3 of rows) {
1414
+ entries.push({ type: "data", row: row3 });
1415
+ }
1416
+ }
1417
+ }
1418
+ return entries;
1419
+ }, [groupedMap, expandedGroups, sortedData]);
1420
+ const visibleDataRows = useMemo2(
1421
+ () => flatEntries.filter((e) => e.type === "data").map((e) => e.row),
1422
+ [flatEntries]
1423
+ );
1315
1424
  const exportToExcel = useCallback2(
1316
1425
  async (options) => {
1317
1426
  const XLSX = await import("xlsx");
@@ -1345,13 +1454,13 @@ function DataGridInner({
1345
1454
  }, [columns, data]);
1346
1455
  const hasFilterableColumns = columns.some((c) => c.filterable);
1347
1456
  const showPagination = page !== void 0 && pageSize !== void 0 && onPageChange !== void 0;
1348
- const allSelected = sortedData.length > 0 && sortedData.every((r) => selectedIds.has(getRowId(r)));
1349
- const someSelected = !allSelected && sortedData.some((r) => selectedIds.has(getRowId(r)));
1457
+ const allSelected = visibleDataRows.length > 0 && visibleDataRows.every((r) => selectedIds.has(getRowId(r)));
1458
+ const someSelected = !allSelected && visibleDataRows.some((r) => selectedIds.has(getRowId(r)));
1350
1459
  const toggleAll = () => {
1351
1460
  if (allSelected) {
1352
1461
  setSelectedIds(/* @__PURE__ */ new Set());
1353
1462
  } else {
1354
- setSelectedIds(new Set(sortedData.map(getRowId)));
1463
+ setSelectedIds(new Set(visibleDataRows.map(getRowId)));
1355
1464
  }
1356
1465
  };
1357
1466
  const toggleRow = (id) => {
@@ -1476,11 +1585,47 @@ function DataGridInner({
1476
1585
  setEditingRowId(null);
1477
1586
  setEditDraft({});
1478
1587
  };
1479
- const totalHeight = sortedData.length * rowHeight;
1480
- const visibleCount = Math.ceil(height / rowHeight) + overscan * 2;
1481
- const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
1482
- const endIndex = Math.min(sortedData.length, startIndex + visibleCount);
1483
- const visibleRows = sortedData.slice(startIndex, endIndex);
1588
+ const resolvedGroupRowHeight = groupRowHeight ?? rowHeight;
1589
+ const rowOffsets = groupedMap ? (() => {
1590
+ const offsets = new Array(flatEntries.length);
1591
+ let acc = 0;
1592
+ for (let i = 0; i < flatEntries.length; i++) {
1593
+ offsets[i] = acc;
1594
+ acc += flatEntries[i].type === "group" ? resolvedGroupRowHeight : rowHeight;
1595
+ }
1596
+ return offsets;
1597
+ })() : null;
1598
+ function findIndexAtOffset(offsets, target) {
1599
+ let lo = 0;
1600
+ let hi = offsets.length - 1;
1601
+ let ans = 0;
1602
+ while (lo <= hi) {
1603
+ const mid = lo + hi >> 1;
1604
+ if (offsets[mid] <= target) {
1605
+ ans = mid;
1606
+ lo = mid + 1;
1607
+ } else {
1608
+ hi = mid - 1;
1609
+ }
1610
+ }
1611
+ return ans;
1612
+ }
1613
+ let totalHeight;
1614
+ let startIndex;
1615
+ let endIndex;
1616
+ if (rowOffsets) {
1617
+ totalHeight = rowOffsets.length ? rowOffsets[rowOffsets.length - 1] + (flatEntries[flatEntries.length - 1].type === "group" ? resolvedGroupRowHeight : rowHeight) : 0;
1618
+ const rawStart = rowOffsets.length ? findIndexAtOffset(rowOffsets, scrollTop) : 0;
1619
+ startIndex = Math.max(0, rawStart - overscan);
1620
+ const rawEnd = rowOffsets.length ? findIndexAtOffset(rowOffsets, scrollTop + height) + 1 : 0;
1621
+ endIndex = Math.min(flatEntries.length, rawEnd + overscan);
1622
+ } else {
1623
+ totalHeight = sortedData.length * rowHeight;
1624
+ const visibleCount = Math.ceil(height / rowHeight) + overscan * 2;
1625
+ startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
1626
+ endIndex = Math.min(flatEntries.length, startIndex + visibleCount);
1627
+ }
1628
+ const visibleEntries = flatEntries.slice(startIndex, endIndex);
1484
1629
  const totalWidth = (selectable ? CHECKBOX_COLUMN_WIDTH : 0) + (editingEnabled ? ACTIONS_COLUMN_WIDTH : 0) + columns.reduce((sum, c) => sum + widthOf(c), 0);
1485
1630
  return /* @__PURE__ */ jsxs17(Fragment5, { children: [
1486
1631
  /* @__PURE__ */ jsxs17("div", { className: gridWrapper, children: [
@@ -1654,13 +1799,64 @@ function DataGridInner({
1654
1799
  width: totalWidth,
1655
1800
  minWidth: "100%"
1656
1801
  },
1657
- children: visibleRows.map((rowData, i) => {
1802
+ children: visibleEntries.map((entry, i) => {
1658
1803
  const index = startIndex + i;
1804
+ const top = rowOffsets ? rowOffsets[index] : index * rowHeight;
1805
+ if (entry.type === "group") {
1806
+ const isExpanded = !entry.collapsed;
1807
+ const groupLabelText = groupColumn ? typeof groupColumn.header === "string" ? groupColumn.header : groupColumn.key : groupBy;
1808
+ const aggregateColumns = columns.filter((c) => c.aggregate);
1809
+ return /* @__PURE__ */ jsxs17(
1810
+ "div",
1811
+ {
1812
+ className: groupRow,
1813
+ style: {
1814
+ top,
1815
+ height: resolvedGroupRowHeight,
1816
+ width: totalWidth
1817
+ },
1818
+ role: "row",
1819
+ children: [
1820
+ /* @__PURE__ */ jsx34(
1821
+ "button",
1822
+ {
1823
+ type: "button",
1824
+ className: groupChevron,
1825
+ "data-expanded": isExpanded || void 0,
1826
+ onClick: () => toggleGroupExpanded(
1827
+ entry.key,
1828
+ Array.from(groupedMap.keys())
1829
+ ),
1830
+ "aria-label": isExpanded ? `Collapse group ${entry.key}` : `Expand group ${entry.key}`,
1831
+ children: "\u203A"
1832
+ }
1833
+ ),
1834
+ /* @__PURE__ */ jsxs17("span", { className: groupLabel, children: [
1835
+ groupLabelText,
1836
+ ": ",
1837
+ entry.key
1838
+ ] }),
1839
+ /* @__PURE__ */ jsxs17("span", { className: groupCount, children: [
1840
+ entry.rows.length,
1841
+ " row",
1842
+ entry.rows.length === 1 ? "" : "s"
1843
+ ] }),
1844
+ aggregateColumns.length > 0 ? /* @__PURE__ */ jsx34("span", { className: groupAggregates, children: aggregateColumns.map((c) => /* @__PURE__ */ jsxs17("span", { className: groupAggregateItem, children: [
1845
+ typeof c.header === "string" ? c.header : c.key,
1846
+ ": ",
1847
+ computeAggregate(c, entry.rows)
1848
+ ] }, c.key)) }) : null
1849
+ ]
1850
+ },
1851
+ `group-${entry.key}`
1852
+ );
1853
+ }
1854
+ const rowData = entry.row;
1659
1855
  const id = getRowId(rowData);
1660
1856
  const isSelected = selectedIds.has(id);
1661
1857
  const isEditing = editingRowId === id;
1662
1858
  const style = {
1663
- top: index * rowHeight,
1859
+ top,
1664
1860
  height: rowHeight,
1665
1861
  width: totalWidth
1666
1862
  };
@@ -1672,6 +1868,8 @@ function DataGridInner({
1672
1868
  role: "row",
1673
1869
  "data-selected": isSelected || void 0,
1674
1870
  "data-editing": isEditing || void 0,
1871
+ onClick: onRowClick ? (e) => onRowClick(rowData, e) : void 0,
1872
+ onDoubleClick: onRowDoubleClick ? (e) => onRowDoubleClick(rowData, e) : void 0,
1675
1873
  children: [
1676
1874
  selectable ? /* @__PURE__ */ jsx34(
1677
1875
  "div",
@@ -1679,6 +1877,7 @@ function DataGridInner({
1679
1877
  className: [checkboxCell, pinnedCell].join(" "),
1680
1878
  style: { left: 0 },
1681
1879
  role: "cell",
1880
+ onClick: (e) => e.stopPropagation(),
1682
1881
  children: /* @__PURE__ */ jsx34(
1683
1882
  "input",
1684
1883
  {
@@ -1699,6 +1898,7 @@ function DataGridInner({
1699
1898
  ...pinnedStyle(column)
1700
1899
  },
1701
1900
  role: "cell",
1901
+ onClick: onCellClick ? (e) => onCellClick(rowData, column, e) : void 0,
1702
1902
  children: isEditing && column.editable ? /* @__PURE__ */ jsx34(
1703
1903
  "input",
1704
1904
  {
@@ -1726,6 +1926,7 @@ function DataGridInner({
1726
1926
  className: [actionsCell, pinnedCell].join(" "),
1727
1927
  style: { right: 0 },
1728
1928
  role: "cell",
1929
+ onClick: (e) => e.stopPropagation(),
1729
1930
  children: isEditing ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
1730
1931
  /* @__PURE__ */ jsx34(
1731
1932
  "button",