@jielga/tmdatagrid 2.0.1 → 2.1.1

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.
@@ -48,8 +48,8 @@ export function createSelectColumn<TData extends RowData>(
48
48
  enablePinning: false,
49
49
  header: ({ table }) => <SelectAllHeader table={table} />,
50
50
  cell: ({ row }) => <SelectRowCheckbox row={row} />,
51
- // Every cell on a group row that is not the grouped column counts as
52
- // aggregated, this lane included, and an aggregated cell with nothing
51
+ // Every cell on a group row that is not the grouped column is a group
52
+ // summary cell, this lane included, and a summary cell with nothing
53
53
  // declared renders blank. Without this the checkbox would disappear from
54
54
  // exactly the rows that select a whole group. See renderCellContent.
55
55
  aggregatedCell: ({ row }) => <SelectRowCheckbox row={row} />,
@@ -90,23 +90,25 @@ export function createGroupColumn<TData extends RowData>(
90
90
  // Structurally pinned to the left; users shouldn't be able to move it.
91
91
  enablePinning: false,
92
92
  cell: ({ row }) => <GroupCell row={row} />,
93
- // A group row has subRows, so every cell on it that is not the grouped
94
- // column reports `getIsAggregated()` - this lane included. Without an
95
- // `aggregatedCell` the body would take that as "nothing to summarise" and
96
- // render the tree lane blank on exactly the rows it exists for.
93
+ // Every cell on a group row that is not the grouped column is a group
94
+ // summary cell - this lane included. Without an `aggregatedCell` the body
95
+ // would take that as "nothing to summarise" and render the tree lane
96
+ // blank on exactly the rows it exists for. See renderCellContent.
97
97
  aggregatedCell: ({ row }) => <GroupCell row={row} />,
98
98
  header: ({ table }) => <GroupHeader table={table} />,
99
99
  };
100
100
  }
101
101
 
102
102
  /**
103
- * The generated details lane, prepended whenever `renderDetails` is set.
103
+ * The generated details lane, added whenever `renderDetails` is set.
104
104
  *
105
105
  * Structural, like the checkbox and tree columns: fixed width, pinned to the
106
- * left after both of them, not hideable, not movable and not resizable. Moving
107
- * or hiding the toggle would leave rows with panels that cannot be opened.
106
+ * edge `detailsColumnPosition` names, not hideable, not movable and not
107
+ * resizable. Moving or hiding the toggle would leave rows with panels that
108
+ * cannot be opened.
108
109
  *
109
- * Last of the three because it acts on a single record.
110
+ * Innermost on its edge - last of the left lanes, or first of the right ones,
111
+ * inside the edit lane - because it acts on a single record.
110
112
  *
111
113
  * A second toggle elsewhere is supported: `row.toggleExpanded()` is the entire
112
114
  * interface, and this lane is only the control the grid ships.
@@ -119,7 +121,7 @@ export function createDetailsColumn<TData extends RowData>(
119
121
  meta: {
120
122
  label,
121
123
  align: "center",
122
- // Structurally the last of the generated lanes.
124
+ // Structurally the innermost of the generated lanes.
123
125
  enableOrdering: false,
124
126
  },
125
127
  // A system lane: as wide as the control it holds and no wider. Fixed at
@@ -135,12 +137,12 @@ export function createDetailsColumn<TData extends RowData>(
135
137
  // Its visibility is not the user's to set: hiding it would strand every
136
138
  // panel behind a control that is no longer there.
137
139
  enableHiding: false,
138
- // Structurally pinned to the left; users shouldn't be able to move it.
140
+ // Structurally pinned to its edge; users shouldn't be able to move it.
139
141
  enablePinning: false,
140
142
  header: ({ table }) => <DetailsHeader table={table} />,
141
143
  cell: ({ row }) => <DetailsCell row={row} />,
142
144
  // Deliberately no `aggregatedCell`: on a group row every cell outside the
143
- // grouped column counts as aggregated, and blank is the right answer here -
145
+ // grouped column is a summary cell, and blank is the right answer here -
144
146
  // groups expand into their rows, not into a panel.
145
147
  };
146
148
  }
@@ -180,7 +182,7 @@ export function createEditColumn<TData extends RowData>(
180
182
  header: () => null,
181
183
  cell: ({ row }) => <EditLaneCell row={row} />,
182
184
  // Group rows: same reasoning as the checkbox lane - without this the
183
- // cell renders blank on aggregated rows, but here blank is also correct,
185
+ // cell renders blank on group rows, but here blank is also correct,
184
186
  // so the aggregated cell renders the same (null for groups).
185
187
  aggregatedCell: ({ row }) => <EditLaneCell row={row} />,
186
188
  };
@@ -12,6 +12,7 @@ import {
12
12
  columnGroupingFeature,
13
13
  columnOrderingFeature,
14
14
  columnPinningFeature,
15
+ type ColumnPinningState,
15
16
  columnResizingFeature,
16
17
  columnSizingFeature,
17
18
  columnVisibilityFeature,
@@ -77,7 +78,11 @@ import {
77
78
  type TMDataGridColumnType,
78
79
  tmDataGridFilterFn,
79
80
  } from "./core/filterOperators";
80
- import { getColumnDefaultOperator, isControlColumn } from "./core/columnUtils";
81
+ import {
82
+ getColumnDefaultOperator,
83
+ isControlColumn,
84
+ isGeneratedColumn,
85
+ } from "./core/columnUtils";
81
86
  import type { TMDataGridColumnFilterOptions } from "./core/filterControls";
82
87
  import {
83
88
  resolveFilterOptions,
@@ -1050,6 +1055,16 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
1050
1055
  * state. An approximate value is enough.
1051
1056
  */
1052
1057
  renderDetailsEstHeight?: number;
1058
+ /**
1059
+ * Which edge the details lane sits on. Defaults to `"left"`.
1060
+ *
1061
+ * `"left"` pins the chevron after the row number, checkbox and tree lanes,
1062
+ * before every column of yours. `"right"` pins it after every column of
1063
+ * yours, inside the edit lane, so the chevron closes the row the way an
1064
+ * accordion's does. On either edge it stays a structural lane: the user
1065
+ * cannot move, hide or unpin it.
1066
+ */
1067
+ detailsColumnPosition?: "left" | "right";
1053
1068
  /**
1054
1069
  * Rows the virtualizer keeps mounted above and below the viewport. Defaults
1055
1070
  * to 6.
@@ -1149,6 +1164,7 @@ export function useTMDataGrid<TData extends RowData>({
1149
1164
  editing,
1150
1165
  renderDetails,
1151
1166
  renderDetailsEstHeight = DEFAULT_DETAILS_EST_HEIGHT,
1167
+ detailsColumnPosition = "left",
1152
1168
  overscan = DEFAULT_OVERSCAN,
1153
1169
  ...options
1154
1170
  }: UseTMDataGridOptions<TData>): TMDataGridApi<TData> {
@@ -1222,7 +1238,6 @@ export function useTMDataGrid<TData extends RowData>({
1222
1238
  ],
1223
1239
  );
1224
1240
 
1225
- const pinningEnabled = options.enableColumnPinning !== false;
1226
1241
  const selectColumnEnabled = features.selectColumn;
1227
1242
  const groupColumnEnabled = features.grouping;
1228
1243
  // The lane that opens the panels. Nothing to switch on: a grid with no
@@ -1250,6 +1265,37 @@ export function useTMDataGrid<TData extends RowData>({
1250
1265
  // rest hold two - it gets the wider track.
1251
1266
  const editWideLane = editDraft;
1252
1267
 
1268
+ // The generated lanes are structurally pinned, outermost first on each edge,
1269
+ // and re-applied on top of anything restored: a snapshot cannot unpin one,
1270
+ // and a snapshot taken before `detailsColumnPosition` changed cannot pin the
1271
+ // details lane to the edge it has since left.
1272
+ //
1273
+ // Not gated on `enableColumnPinning`: that switch takes pinning away from the
1274
+ // user, not from the grid's own lanes, which hold the edges either way.
1275
+ const structuralPinning = (
1276
+ restored: Partial<ColumnPinningState> | undefined,
1277
+ ): ColumnPinningState => {
1278
+ const own = (ids: string[] | undefined) =>
1279
+ (ids ?? []).filter((id) => !isGeneratedColumn(id));
1280
+ const lane = (enabled: boolean, id: string) => (enabled ? [id] : []);
1281
+ const detailsOn = (edge: "left" | "right") =>
1282
+ detailsColumnEnabled && detailsColumnPosition === edge;
1283
+ return {
1284
+ start: [
1285
+ ...lane(rowNumbersEnabled, ROW_NUMBER_COLUMN_ID),
1286
+ ...lane(selectColumnEnabled, SELECT_COLUMN_ID),
1287
+ ...lane(groupColumnEnabled, GROUP_COLUMN_ID),
1288
+ ...lane(detailsOn("left"), DETAILS_COLUMN_ID),
1289
+ ...own(restored?.start),
1290
+ ],
1291
+ end: [
1292
+ ...own(restored?.end),
1293
+ ...lane(detailsOn("right"), DETAILS_COLUMN_ID),
1294
+ ...lane(editColumnEnabled, EDIT_COLUMN_ID),
1295
+ ],
1296
+ };
1297
+ };
1298
+
1253
1299
  const columns = useMemo(() => {
1254
1300
  const base = withTMDataGridDefaults<TData>(
1255
1301
  options.columns as ReadonlyArray<TMDataGridColumnDef<TData>>,
@@ -1262,8 +1308,12 @@ export function useTMDataGrid<TData extends RowData>({
1262
1308
  //
1263
1309
  // The order is the order they are pinned in, and it follows what each one
1264
1310
  // is about: tick a row, find it in the tree the user grouped it into, then
1265
- // open it. The details chevron sits last because it acts on the record the
1266
- // lanes to its left have narrowed down to.
1311
+ // open it. The details chevron sits innermost on whichever edge it was
1312
+ // given - last of the left lanes, or first of the right ones - because it
1313
+ // acts on the record the lanes outside it have narrowed down to.
1314
+ const detailsColumn = detailsColumnEnabled
1315
+ ? [createDetailsColumn<TData>(detailsColumnLabel)]
1316
+ : [];
1267
1317
  return [
1268
1318
  // The gutter sits outside everything, the way a spreadsheet's does.
1269
1319
  ...(rowNumbersEnabled
@@ -1273,10 +1323,9 @@ export function useTMDataGrid<TData extends RowData>({
1273
1323
  ? [createSelectColumn<TData>(selectColumnLabel)]
1274
1324
  : []),
1275
1325
  ...(groupColumnEnabled ? [createGroupColumn<TData>(groupColumnLabel)] : []),
1276
- ...(detailsColumnEnabled
1277
- ? [createDetailsColumn<TData>(detailsColumnLabel)]
1278
- : []),
1326
+ ...(detailsColumnPosition === "left" ? detailsColumn : []),
1279
1327
  ...base,
1328
+ ...(detailsColumnPosition === "right" ? detailsColumn : []),
1280
1329
  // Last and pinned right - the row's Save belongs at the end of the row.
1281
1330
  ...(editColumnEnabled
1282
1331
  ? [createEditColumn<TData>(editColumnLabel, editWideLane)]
@@ -1287,6 +1336,7 @@ export function useTMDataGrid<TData extends RowData>({
1287
1336
  rowNumbersEnabled,
1288
1337
  selectColumnEnabled,
1289
1338
  detailsColumnEnabled,
1339
+ detailsColumnPosition,
1290
1340
  groupColumnEnabled,
1291
1341
  editColumnEnabled,
1292
1342
  editWideLane,
@@ -1619,37 +1669,14 @@ export function useTMDataGrid<TData extends RowData>({
1619
1669
  ? { [GROUP_COLUMN_ID]: initialGrouping.length > 0 }
1620
1670
  : {}),
1621
1671
  },
1622
- columnPinning: {
1623
- // The generated columns are structurally pinned, so they are re-applied
1624
- // on top of anything restored from storage.
1625
- start: [
1626
- ...(rowNumbersEnabled && pinningEnabled ? [ROW_NUMBER_COLUMN_ID] : []),
1627
- ...(selectColumnEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
1628
- ...(groupColumnEnabled && pinningEnabled ? [GROUP_COLUMN_ID] : []),
1629
- ...(detailsColumnEnabled && pinningEnabled ? [DETAILS_COLUMN_ID] : []),
1630
- ...(
1631
- persistedState.columnPinning?.start ??
1632
- options.initialState?.columnPinning?.start ??
1633
- []
1634
- ).filter(
1635
- (id) =>
1636
- id !== ROW_NUMBER_COLUMN_ID &&
1637
- id !== SELECT_COLUMN_ID &&
1638
- id !== DETAILS_COLUMN_ID &&
1639
- id !== GROUP_COLUMN_ID,
1640
- ),
1641
- ],
1642
- // The edit lane mirrors the generated columns on the left: structurally
1643
- // pinned, outermost, re-applied over anything restored.
1644
- end: [
1645
- ...(
1646
- persistedState.columnPinning?.end ??
1647
- options.initialState?.columnPinning?.end ??
1648
- []
1649
- ).filter((id) => id !== EDIT_COLUMN_ID),
1650
- ...(editColumnEnabled && pinningEnabled ? [EDIT_COLUMN_ID] : []),
1651
- ],
1652
- },
1672
+ columnPinning: structuralPinning({
1673
+ start:
1674
+ persistedState.columnPinning?.start ??
1675
+ options.initialState?.columnPinning?.start,
1676
+ end:
1677
+ persistedState.columnPinning?.end ??
1678
+ options.initialState?.columnPinning?.end,
1679
+ }),
1653
1680
  pagination: {
1654
1681
  pageIndex: 0,
1655
1682
  pageSize: 25,
@@ -1964,27 +1991,7 @@ export function useTMDataGrid<TData extends RowData>({
1964
1991
  });
1965
1992
  table.setColumnSizing({ ...initial?.columnSizing });
1966
1993
  table.setColumnOrder([...(initial?.columnOrder ?? [])]);
1967
- table.setColumnPinning({
1968
- start: [
1969
- ...(rowNumbersEnabled && pinningEnabled ? [ROW_NUMBER_COLUMN_ID] : []),
1970
- ...(selectColumnEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
1971
- ...(groupColumnEnabled && pinningEnabled ? [GROUP_COLUMN_ID] : []),
1972
- ...(detailsColumnEnabled && pinningEnabled ? [DETAILS_COLUMN_ID] : []),
1973
- ...(initial?.columnPinning?.start ?? []).filter(
1974
- (id) =>
1975
- id !== ROW_NUMBER_COLUMN_ID &&
1976
- id !== SELECT_COLUMN_ID &&
1977
- id !== DETAILS_COLUMN_ID &&
1978
- id !== GROUP_COLUMN_ID,
1979
- ),
1980
- ],
1981
- end: [
1982
- ...(initial?.columnPinning?.end ?? []).filter(
1983
- (id) => id !== EDIT_COLUMN_ID,
1984
- ),
1985
- ...(editColumnEnabled && pinningEnabled ? [EDIT_COLUMN_ID] : []),
1986
- ],
1987
- });
1994
+ table.setColumnPinning(structuralPinning(initial?.columnPinning));
1988
1995
  };
1989
1996
  const resetSettings = useCallback(() => resetSettingsRef.current(), []);
1990
1997