@jielga/tmdatagrid 1.0.2 → 1.1.0

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.
@@ -24,7 +24,10 @@ import classes from "./TMDataGridTable.module.css";
24
24
  import sticky from "./sticky.module.css";
25
25
  import { type TMDataGridRowData, useTMDataGridContext } from "../TMDataGridContext";
26
26
  import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
27
- import { TMDataGridHeaderCell } from "./TMDataGridHeaderCell";
27
+ import {
28
+ TMDataGridHeaderCell,
29
+ type TMDataGridColumnMenuItemsRenderer,
30
+ } from "./TMDataGridHeaderCell";
28
31
  import {
29
32
  TMDataGridCellEditor,
30
33
  type TMDataGridCellEditorClose,
@@ -386,7 +389,7 @@ function TMDataGridBodyCell({
386
389
 
387
390
  /**
388
391
  * The virtualized body rows, wrapped in the single Menu that serves all of them
389
- * when `rowContextMenu` is set.
392
+ * when `renderRowContextMenu` is set.
390
393
  *
391
394
  * One instance for the whole body rather than one per row. A Mantine `Menu` is
392
395
  * a `Popover`, and a closed Popover still runs its hooks on every render - and
@@ -447,7 +450,7 @@ function TMDataGridBodyRowGroup({
447
450
  );
448
451
  }
449
452
 
450
- /** What a `rowContextMenu` render prop is handed for the right-clicked row. */
453
+ /** What `renderRowContextMenu` is handed for the right-clicked row. */
451
454
  export type TMDataGridRowContextMenuArgs<TData extends RowData> = {
452
455
  table: Table<TMDataGridFeatures, TData>;
453
456
  row: Row<TMDataGridFeatures, TData>;
@@ -462,6 +465,14 @@ export type TMDataGridRowContextMenuArgs<TData extends RowData> = {
462
465
  * `closeOnItemClick`), so this is for content that isn't a menu item.
463
466
  */
464
467
  close: () => void;
468
+ /**
469
+ * The grid's own items for this menu: the cell copy and export items, under
470
+ * `cellSelection: "range"`, and `null` otherwise.
471
+ *
472
+ * Placing it decides the order. Ignore it and the grid keeps today's
473
+ * behavior, rendering its items above a divider and yours below.
474
+ */
475
+ internalItems: ReactNode;
465
476
  };
466
477
 
467
478
  /**
@@ -469,7 +480,7 @@ export type TMDataGridRowContextMenuArgs<TData extends RowData> = {
469
480
  * `Menu.Divider`, `Menu.Sub`, or any other node. The grid owns the `Menu` and
470
481
  * its `Menu.Dropdown`; this fills the dropdown.
471
482
  */
472
- export type TMDataGridRowContextMenu<TData extends RowData> = (
483
+ export type TMDataGridRowContextMenuRenderer<TData extends RowData> = (
473
484
  args: TMDataGridRowContextMenuArgs<TData>,
474
485
  ) => ReactNode;
475
486
 
@@ -509,7 +520,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
509
520
  /** As {@link onCellClick}; a double-click that opens an editor still does. */
510
521
  onCellDoubleClick?: (args: TMDataGridCellEventArgs<TData>) => void;
511
522
  /**
512
- * As {@link onCellClick}; `rowContextMenu` and the cell-selection menu still
523
+ * As {@link onCellClick}; `renderRowContextMenu` and the cell-selection menu still
513
524
  * open. To suppress the browser's own menu on a grid with neither, call
514
525
  * `event.preventDefault()` here.
515
526
  */
@@ -575,7 +586,7 @@ export type TMDataGridTableProps<TData extends RowData> = {
575
586
  *
576
587
  * ```tsx
577
588
  * <TMDataGrid.Table<Employee>
578
- * rowContextMenu={({ row, close }) => (
589
+ * renderRowContextMenu={({ row, close }) => (
579
590
  * <>
580
591
  * <Menu.Label>{row.original.firstName}</Menu.Label>
581
592
  * <Menu.Item onClick={() => open(row.original.id)}>Open</Menu.Item>
@@ -588,10 +599,33 @@ export type TMDataGridTableProps<TData extends RowData> = {
588
599
  * />
589
600
  * ```
590
601
  *
602
+ * `internalItems` is the grid's own half - place it to control the order,
603
+ * ignore it and the grid keeps its items above a divider and yours below.
604
+ *
591
605
  * Called during render, and only for the row whose menu is open - so it stays
592
606
  * a pure function of the row, and cost per row does not matter.
593
607
  */
594
- rowContextMenu?: TMDataGridRowContextMenu<TData>;
608
+ renderRowContextMenu?: TMDataGridRowContextMenuRenderer<TData>;
609
+ /**
610
+ * Replaces a column menu's contents, taking the grid's own items and
611
+ * returning the full list.
612
+ *
613
+ * ```tsx
614
+ * <TMDataGrid.Table
615
+ * renderColumnMenuItems={({ column, internalItems }) => [
616
+ * ...internalItems,
617
+ * <Menu.Divider key="custom" />,
618
+ * <Menu.Item key="stats" onClick={() => showStats(column.id)}>
619
+ * Statistics
620
+ * </Menu.Item>,
621
+ * ]}
622
+ * />
623
+ * ```
624
+ *
625
+ * Returning an empty list leaves the column with no menu button, the same as
626
+ * a column whose every feature is switched off.
627
+ */
628
+ renderColumnMenuItems?: TMDataGridColumnMenuItemsRenderer;
595
629
  /**
596
630
  * Passed to the Mantine `Menu` the grid wraps the row in - `width`, `shadow`,
597
631
  * `position`, `transitionProps` and the rest. Its open state is the grid's.
@@ -651,7 +685,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
651
685
  onScrollToLeft,
652
686
  onScrollToRight,
653
687
  renderEmptyState,
654
- rowContextMenu,
688
+ renderRowContextMenu,
689
+ renderColumnMenuItems,
655
690
  rowContextMenuProps,
656
691
  cellExport,
657
692
  onReachEnd,
@@ -1714,29 +1749,13 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
1714
1749
  ? undefined
1715
1750
  : rows.find((row) => row.id === contextMenuTarget.rowId);
1716
1751
 
1717
- /**
1718
- * Built for the open row only, so a consumer's render prop is called once per
1719
- * opened menu rather than once per mounted row. `null` back from it means
1720
- * this row has no menu, which is also how the menu stays shut.
1721
- *
1722
- * The casts put back the concrete row type the context boundary erased - the
1723
- * same crossing `onRowClick` makes.
1724
- */
1725
- const rowMenuContent =
1726
- rowContextMenu &&
1727
- contextMenuTarget &&
1728
- contextMenuRow &&
1729
- !contextMenuRow.getIsGrouped()
1730
- ? rowContextMenu({
1731
- table: table as unknown as Table<TMDataGridFeatures, TData>,
1732
- row: contextMenuRow as unknown as Row<TMDataGridFeatures, TData>,
1733
- cell: (contextMenuRow
1734
- .getAllCells()
1735
- .find((cell) => cell.column.id === contextMenuTarget.columnId) ??
1736
- null) as unknown as Cell<TMDataGridFeatures, TData, unknown> | null,
1737
- close: closeContextMenu,
1738
- }) ?? null
1739
- : null;
1752
+ const canRenderRowMenu =
1753
+ renderRowContextMenu !== undefined &&
1754
+ contextMenuTarget !== null &&
1755
+ contextMenuRow !== undefined &&
1756
+ !contextMenuRow.getIsGrouped();
1757
+
1758
+
1740
1759
 
1741
1760
  /**
1742
1761
  * The built-in half of the menu: what to do with the cells that are selected.
@@ -1780,18 +1799,56 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
1780
1799
  </>
1781
1800
  ) : null;
1782
1801
 
1802
+ /**
1803
+ * The consumer's half, built for the open row only - so the render prop runs
1804
+ * once per opened menu rather than once per mounted row. `null` back from it
1805
+ * means this row has no menu, which is also how the menu stays shut.
1806
+ *
1807
+ * `internalItems` is a getter, and reading it is the signal: a consumer who
1808
+ * touches it has taken the composition over, so the grid stops prepending
1809
+ * its own half. One who never mentions it gets today's behavior, the cell
1810
+ * items above a divider. The alternative - a second prop declaring intent -
1811
+ * asks people to say twice what the placement already says.
1812
+ *
1813
+ * The casts put back the concrete row type the context boundary erased - the
1814
+ * same crossing `onRowClick` makes.
1815
+ */
1816
+ let tookOverMenu = false;
1817
+ const rowMenuContent = canRenderRowMenu
1818
+ ? (renderRowContextMenu({
1819
+ table: table as unknown as Table<TMDataGridFeatures, TData>,
1820
+ row: contextMenuRow as unknown as Row<TMDataGridFeatures, TData>,
1821
+ cell: (contextMenuRow
1822
+ ?.getAllCells()
1823
+ .find((cell) => cell.column.id === contextMenuTarget?.columnId) ??
1824
+ null) as unknown as Cell<TMDataGridFeatures, TData, unknown> | null,
1825
+ close: closeContextMenu,
1826
+ get internalItems() {
1827
+ tookOverMenu = true;
1828
+ return cellMenuContent;
1829
+ },
1830
+ }) ?? null)
1831
+ : null;
1832
+
1783
1833
  /**
1784
1834
  * The two halves, in the order they are about: the cells first, since the
1785
1835
  * right-click landed on one, then whatever the consumer offers for the row.
1836
+ * Unless the consumer took the composition over, in which case what they
1837
+ * returned is the whole menu.
1786
1838
  */
1787
- const contextMenuContent =
1788
- cellMenuContent === null && rowMenuContent === null ? null : (
1789
- <>
1790
- {cellMenuContent}
1791
- {cellMenuContent !== null && rowMenuContent !== null && <Menu.Divider />}
1792
- {rowMenuContent}
1793
- </>
1794
- );
1839
+ const contextMenuContent = tookOverMenu
1840
+ ? rowMenuContent
1841
+ : cellMenuContent === null && rowMenuContent === null
1842
+ ? null
1843
+ : (
1844
+ <>
1845
+ {cellMenuContent}
1846
+ {cellMenuContent !== null && rowMenuContent !== null && (
1847
+ <Menu.Divider />
1848
+ )}
1849
+ {rowMenuContent}
1850
+ </>
1851
+ );
1795
1852
 
1796
1853
  /**
1797
1854
  * Which edges the scroll position is currently at. Seeded "at top-left",
@@ -1908,6 +1965,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
1908
1965
  >
1909
1966
  {headerGroup.headers.map((header) => (
1910
1967
  <TMDataGridHeaderCell
1968
+ renderColumnMenuItems={renderColumnMenuItems}
1911
1969
  key={header.id}
1912
1970
  header={header}
1913
1971
  layout={layoutFor(header.column.id)}
@@ -1929,8 +1987,8 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
1929
1987
 
1930
1988
  <TMDataGridBodyRowGroup
1931
1989
  // Under `"range"` the grid has a menu of its own to offer, so the
1932
- // wrapper goes on even without a consumer's `rowContextMenu`.
1933
- enabled={rowContextMenu !== undefined || cellRangeSelection}
1990
+ // wrapper goes on even without a consumer's `renderRowContextMenu`.
1991
+ enabled={renderRowContextMenu !== undefined || cellRangeSelection}
1934
1992
  menuContent={contextMenuContent}
1935
1993
  menuProps={rowContextMenuProps}
1936
1994
  onContextMenu={handleContextMenu}
@@ -2110,14 +2168,14 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
2110
2168
  // is recorded before the dropdown is built.
2111
2169
  //
2112
2170
  // A group row records itself only for the cell menu, which is
2113
- // about the cells it covers. `rowContextMenu` is still never
2171
+ // about the cells it covers. `renderRowContextMenu` is still never
2114
2172
  // called for one: TanStack builds a group row on its first
2115
2173
  // child's record, so a render prop reaching for `row.original`
2116
2174
  // would be handed a real employee that has nothing to do with
2117
2175
  // the group - see where the content is built. The browser menu
2118
2176
  // stays suppressed over them either way.
2119
2177
  onContextMenu={
2120
- (rowContextMenu && !isGroupRow) || cellRangeSelection
2178
+ (renderRowContextMenu && !isGroupRow) || cellRangeSelection
2121
2179
  ? () => {
2122
2180
  contextMenuRowRef.current = row.id;
2123
2181
  }
@@ -2252,7 +2310,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
2252
2310
  : undefined
2253
2311
  }
2254
2312
  onContextMenu={
2255
- rowContextMenu || cellRangeSelection || onCellContextMenu
2313
+ renderRowContextMenu || cellRangeSelection || onCellContextMenu
2256
2314
  ? (event) => {
2257
2315
  contextMenuColumnRef.current = cell.column.id;
2258
2316
  if (isGroupRow) return;
@@ -66,7 +66,14 @@ export {
66
66
  type TMDataGridOptionsArgs,
67
67
  type TMDataGridOptionsSource,
68
68
  } from "./core/columnOptions";
69
- export { TMDataGridEditActions } from "./components/TMDataGridEditActions";
69
+ export {
70
+ TMDataGridEditActions,
71
+ type TMDataGridEditActionsActions,
72
+ type TMDataGridEditActionsControls,
73
+ type TMDataGridEditActionsProps,
74
+ type TMDataGridEditActionsSlotArgs,
75
+ type TMDataGridEditActionsState,
76
+ } from "./components/TMDataGridEditActions";
70
77
  export {
71
78
  clearedValueForType,
72
79
  getEditFieldName,
@@ -207,13 +214,21 @@ export type {
207
214
  TMDataGridCellEventArgs,
208
215
  TMDataGridCellNav,
209
216
  TMDataGridColumnLayout,
210
- TMDataGridRowContextMenu,
211
217
  TMDataGridRowContextMenuArgs,
218
+ TMDataGridRowContextMenuRenderer,
212
219
  TMDataGridRowStyle,
213
220
  TMDataGridTableProps,
214
221
  } from "./components/TMDataGridTable";
222
+ export type {
223
+ TMDataGridColumnMenuItemsArgs,
224
+ TMDataGridColumnMenuItemsRenderer,
225
+ } from "./components/TMDataGridHeaderCell";
215
226
  export {
216
227
  getTMDataGridPaginationApi,
217
228
  type TMDataGridFooterProps,
229
+ type TMDataGridPaginationActions,
218
230
  type TMDataGridPaginationApi,
231
+ type TMDataGridPaginationControls,
232
+ type TMDataGridPaginationSlotArgs,
233
+ type TMDataGridPaginationState,
219
234
  } from "./components/TMDataGridFooter";