@dxos/react-ui-table 0.4.3-main.f97384b → 0.4.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"TableFooter.d.ts","sourceRoot":"","sources":["../../../../../src/components/Table/TableFooter.tsx"],"names":[],"mappings":"AAUA,MAAM,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAIlC,QAAA,MAAM,WAAW;mCAAkC,gBAAgB;;CAuBlE,CAAC;AAIF,OAAO,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"TableFooter.d.ts","sourceRoot":"","sources":["../../../../../src/components/Table/TableFooter.tsx"],"names":[],"mappings":"AAUA,KAAK,gBAAgB,GAAG,EAAE,CAAC;AAI3B,QAAA,MAAM,WAAW;mCAAkC,gBAAgB;;CAwBlE,CAAC;AAIF,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TableHead.d.ts","sourceRoot":"","sources":["../../../../../src/components/Table/TableHead.tsx"],"names":[],"mappings":"AAcA,KAAK,cAAc,GAAG,EAAE,CAAC;AAEzB,QAAA,MAAM,SAAS;oCAAmC,cAAc;;CAwD/D,CAAC;AAIF,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"TableHead.d.ts","sourceRoot":"","sources":["../../../../../src/components/Table/TableHead.tsx"],"names":[],"mappings":"AAcA,KAAK,cAAc,GAAG,EAAE,CAAC;AAEzB,QAAA,MAAM,SAAS;oCAAmC,cAAc;;CA0D/D,CAAC;AAIF,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,cAAc,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-table",
3
- "version": "0.4.3-main.f97384b",
3
+ "version": "0.4.3",
4
4
  "description": "A table component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -22,14 +22,14 @@
22
22
  "@tanstack/react-table": "^8.9.3",
23
23
  "@tanstack/react-virtual": "^3.0.1",
24
24
  "date-fns": "^3.3.1",
25
- "@dxos/async": "0.4.3-main.f97384b",
26
- "@dxos/keys": "0.4.3-main.f97384b",
27
- "@dxos/invariant": "0.4.3-main.f97384b",
28
- "@dxos/react-ui": "0.4.3-main.f97384b",
29
- "@dxos/react-ui-theme": "0.4.3-main.f97384b",
30
- "@dxos/react-ui-searchlist": "0.4.3-main.f97384b",
31
- "@dxos/react-ui-types": "0.4.3-main.f97384b",
32
- "@dxos/util": "0.4.3-main.f97384b"
25
+ "@dxos/async": "0.4.3",
26
+ "@dxos/keys": "0.4.3",
27
+ "@dxos/invariant": "0.4.3",
28
+ "@dxos/util": "0.4.3",
29
+ "@dxos/react-ui-searchlist": "0.4.3",
30
+ "@dxos/react-ui": "0.4.3",
31
+ "@dxos/react-ui-types": "0.4.3",
32
+ "@dxos/react-ui-theme": "0.4.3"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@phosphor-icons/react": "^2.0.5",
@@ -40,9 +40,9 @@
40
40
  "react-dom": "^18.2.0",
41
41
  "vite": "^5.0.12",
42
42
  "vite-plugin-turbosnap": "^1.0.2",
43
- "@dxos/random": "0.4.3-main.f97384b",
44
- "@dxos/storybook-utils": "0.4.3-main.f97384b",
45
- "@dxos/react-ui-theme": "0.4.3-main.f97384b"
43
+ "@dxos/react-ui-theme": "0.4.3",
44
+ "@dxos/random": "0.4.3",
45
+ "@dxos/storybook-utils": "0.4.3"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@phosphor-icons/react": "^2.0.5",
@@ -5,14 +5,14 @@
5
5
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
6
6
  import {
7
7
  getCoreRowModel,
8
- getGroupedRowModel,
9
8
  useReactTable,
10
9
  type ColumnSizingInfoState,
11
- type ColumnSizingState,
12
10
  type GroupingState,
13
11
  type RowData,
14
- type RowSelectionState,
12
+ type ColumnSizingState,
15
13
  type OnChangeFn,
14
+ type RowSelectionState,
15
+ getGroupedRowModel,
16
16
  } from '@tanstack/react-table';
17
17
  import { useVirtualizer, type VirtualizerOptions } from '@tanstack/react-virtual';
18
18
  import React, { Fragment, useCallback, useEffect, useState } from 'react';
@@ -26,78 +26,6 @@ import { TableHead } from './TableHead';
26
26
  import { type TableProps } from './props';
27
27
  import { groupTh, tableRoot } from '../../theme';
28
28
 
29
- const VirtualizedTableContent = ({
30
- getScrollElement,
31
- }: Pick<VirtualizerOptions<Element, Element>, 'getScrollElement'>) => {
32
- const {
33
- table: { getRowModel },
34
- } = useTableContext('VirtualizedTableContent');
35
- const rows = getRowModel().rows;
36
-
37
- const { getTotalSize, getVirtualItems } = useVirtualizer({
38
- getScrollElement,
39
- count: rows.length,
40
- overscan: 16,
41
- estimateSize: () => 33,
42
- });
43
- const virtualRows = getVirtualItems();
44
- const totalSize = getTotalSize();
45
-
46
- const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
47
- const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0;
48
-
49
- return (
50
- <>
51
- {paddingTop > 0 && (
52
- <tbody role='none'>
53
- <tr role='none'>
54
- <td style={{ height: `${paddingTop}px` }} role='none' />
55
- </tr>
56
- </tbody>
57
- )}
58
- <TableBody rows={virtualRows.map((virtualRow) => rows[virtualRow.index])} />
59
- {paddingBottom > 0 && (
60
- <tbody role='none'>
61
- <tr role='none'>
62
- <td style={{ height: `${paddingBottom}px` }} role='none' />
63
- </tr>
64
- </tbody>
65
- )}
66
- </>
67
- );
68
- };
69
-
70
- const GroupedTableContent = () => {
71
- const {
72
- table: { getGroupedRowModel, getHeaderGroups, getState },
73
- debug,
74
- } = useTableContext('GroupedTableContent');
75
- return (
76
- <>
77
- {getGroupedRowModel().rows.map((row, i) => {
78
- return (
79
- <Fragment key={i}>
80
- {/* TODO(burdon): Customize group header renderer. */}
81
- <thead>
82
- <tr>
83
- {debug && <th />}
84
- <th
85
- // TODO(burdon): Calculate row span.
86
- colSpan={getHeaderGroups()[0].headers.length}
87
- className={groupTh({})}
88
- >
89
- {getState().grouping[0]}[{String(row.getGroupingValue(getState().grouping[0]))}]
90
- </th>
91
- </tr>
92
- </thead>
93
- <TableBody rows={row.subRows} />
94
- </Fragment>
95
- );
96
- })}
97
- </>
98
- );
99
- };
100
-
101
29
  export const Table = <TData extends RowData>(props: TableProps<TData>) => {
102
30
  const {
103
31
  role,
@@ -106,51 +34,74 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
106
34
  onColumnResize,
107
35
  columnVisibility,
108
36
  header = true,
109
- footer,
110
37
  rowsSelectable,
111
- fullWidth,
112
38
  debug,
113
39
  onDataSelectionChange,
114
- classNames,
115
- getScrollElement,
116
40
  } = props;
117
41
 
118
42
  const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
119
43
 
44
+ //
45
+ // Column resizing
46
+ //
47
+
48
+ const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
49
+ useEffect(() => {
50
+ // Set initial state.
51
+ setColumnSizing(
52
+ columns
53
+ .filter((column) => !!column.size && (column as any).prop !== undefined)
54
+ .reduce<ColumnSizingState>((state, column) => {
55
+ state[(column as any).prop] = column.size!;
56
+ return state;
57
+ }, {}),
58
+ );
59
+ }, [columns]);
60
+
61
+ const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
62
+ const onColumnResizeDebounced = onColumnResize && debounce<ColumnSizingState>(onColumnResize, 1_000);
63
+ useEffect(() => {
64
+ if (columnSizingInfo.columnSizingStart?.length === 0) {
65
+ onColumnResizeDebounced?.(table.getState().columnSizing);
66
+ }
67
+ }, [columnSizingInfo]);
68
+
69
+ //
120
70
  // Row selection
121
- const [rowSelection = {}, directlySetRowSelection] = useControllableState({
71
+ //
72
+
73
+ const [rowSelection = {}, setRowSelection] = useControllableState({
122
74
  prop: props.rowSelection,
123
75
  onChange: props.onRowSelectionChange,
124
76
  defaultProp: props.defaultRowSelection,
125
77
  });
126
78
 
79
+ useEffect(() => {
80
+ onDataSelectionChange?.(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
81
+ }, [onDataSelectionChange, rowSelection]);
82
+
127
83
  // TODO(thure): Does @tanstack/react-table really need this intervention? It did seem necessary to enforce single-selection...
128
84
  const handleRowSelectionChange = useCallback<OnChangeFn<RowSelectionState>>(
129
85
  (updaterOrValue) => {
130
86
  const nextRowSelection = typeof updaterOrValue === 'function' ? updaterOrValue(rowSelection) : updaterOrValue;
131
87
  if (rowsSelectable === 'multi') {
132
- directlySetRowSelection(nextRowSelection);
88
+ setRowSelection(nextRowSelection);
133
89
  } else if (rowsSelectable) {
134
90
  const nextRowSelectionKey = Object.keys(nextRowSelection).filter((id) => !rowSelection[id])[0];
135
- directlySetRowSelection(nextRowSelectionKey ? { [nextRowSelectionKey]: true } : {});
91
+ setRowSelection(nextRowSelectionKey ? { [nextRowSelectionKey]: true } : {});
136
92
  } else {
137
- directlySetRowSelection({});
93
+ setRowSelection({});
138
94
  }
139
95
  },
140
- [rowSelection, directlySetRowSelection],
96
+ [rowSelection, setRowSelection],
141
97
  );
142
98
 
143
- // Resizing
144
- const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
145
- const onColumnResizeDebounced = debounce<ColumnSizingState>((info) => onColumnResize?.(info), 500);
146
- useEffect(() => {
147
- if (columnSizingInfo.columnSizingStart?.length === 0) {
148
- onColumnResizeDebounced(table.getState().columnSizing);
149
- }
150
- }, [columnSizingInfo]);
99
+ //
100
+ // Row grouping
101
+ //
151
102
 
152
- const [grouping, setGrouping] = useState<GroupingState>(props.grouping ?? []);
153
- useEffect(() => setGrouping(props.grouping ?? []), [props.grouping]);
103
+ const [grouping, handleGroupingChange] = useState<GroupingState>(props.grouping ?? []);
104
+ useEffect(() => handleGroupingChange(props.grouping ?? []), [props.grouping]);
154
105
 
155
106
  const table = useReactTable({
156
107
  // Data
@@ -164,20 +115,27 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
164
115
  maxSize: 1024,
165
116
  },
166
117
 
167
- // Rows
168
- getCoreRowModel: getCoreRowModel(),
169
-
170
118
  // State
171
119
  state: {
172
120
  columnVisibility,
121
+ columnSizing,
173
122
  columnSizingInfo,
174
123
  rowSelection,
175
124
  grouping,
176
125
  },
177
126
 
127
+ // Resize columns
128
+ columnResizeMode: 'onChange',
129
+ enableColumnResizing: true,
130
+ onColumnSizingChange: setColumnSizing,
131
+ onColumnSizingInfoChange: setColumnSizingInfo,
132
+
133
+ // Rows
134
+ getCoreRowModel: getCoreRowModel(),
135
+
178
136
  // Grouping
179
- getGroupedRowModel: getGroupedRowModel(),
180
- onGroupingChange: setGrouping,
137
+ getGroupedRowModel: grouping.length > 1 ? getGroupedRowModel() : undefined,
138
+ onGroupingChange: handleGroupingChange,
181
139
 
182
140
  // Selection
183
141
  ...(rowsSelectable === 'multi'
@@ -187,59 +145,133 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
187
145
  : {}),
188
146
  onRowSelectionChange: handleRowSelectionChange,
189
147
 
190
- // Resize columns
191
- columnResizeMode: 'onChange',
192
- enableColumnResizing: true,
193
- onColumnSizingInfoChange: setColumnSizingInfo,
194
-
195
148
  // Debug
196
149
  debugTable: debug,
197
150
  });
198
151
 
199
- const rows = table.getRowModel().rows;
200
-
201
- useEffect(() => {
202
- if (onDataSelectionChange) {
203
- onDataSelectionChange(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
204
- }
205
- }, [onDataSelectionChange, rowSelection, table]);
206
-
207
152
  // Create additional expansion column if all columns have fixed width.
208
153
  const expand = false; // columns.map((column) => column.size).filter(Boolean).length === columns?.length;
209
154
 
210
155
  return (
211
156
  <TableProvider
212
157
  {...props}
158
+ table={table}
213
159
  header={header}
214
160
  expand={expand}
215
- table={table}
216
161
  isGrid={role === 'grid' || role === 'treegrid'}
217
162
  >
218
- <table
219
- role={role}
220
- className={tableRoot(props, classNames)}
221
- {...(!fullWidth && { style: { width: table.getTotalSize() } })}
222
- >
223
- <TableHead />
224
-
225
- {grouping.length === 0 ? (
226
- getScrollElement ? (
227
- <VirtualizedTableContent getScrollElement={getScrollElement} />
228
- ) : (
229
- <TableBody rows={rows} />
230
- )
163
+ <TableImpl<TData> debug={false} />
164
+ </TableProvider>
165
+ );
166
+ };
167
+
168
+ /**
169
+ * Pure implementation of table outside of context set-up.
170
+ */
171
+ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
172
+ const { role, footer, grouping, getScrollElement, fullWidth, classNames, debug } = props;
173
+ const { table } = useTableContext<TData>('TableImpl');
174
+ const { rows } = table.getRowModel();
175
+
176
+ if (debug) {
177
+ return (
178
+ <pre className='font-mono text-xs text-neutral-500 m-1 p-2 ring'>
179
+ <code>{JSON.stringify(table.getState(), undefined, 2)}</code>
180
+ </pre>
181
+ );
182
+ }
183
+
184
+ return (
185
+ <table
186
+ role={role}
187
+ className={tableRoot(props, classNames)}
188
+ {...(!fullWidth && { style: { width: table.getTotalSize() } })}
189
+ >
190
+ <TableHead />
191
+
192
+ {grouping?.length !== 0 ? (
193
+ getScrollElement ? (
194
+ <VirtualizedTableContent getScrollElement={getScrollElement} />
231
195
  ) : (
232
- <GroupedTableContent />
233
- )}
196
+ <TableBody rows={rows} />
197
+ )
198
+ ) : (
199
+ <GroupedTableContent />
200
+ )}
234
201
 
235
- {footer && <TableFooter />}
236
- </table>
202
+ {footer && <TableFooter />}
203
+ </table>
204
+ );
205
+ };
206
+
207
+ const VirtualizedTableContent = ({
208
+ getScrollElement,
209
+ }: Pick<VirtualizerOptions<Element, Element>, 'getScrollElement'>) => {
210
+ const {
211
+ table: { getRowModel },
212
+ } = useTableContext('VirtualizedTableContent');
213
+ const rows = getRowModel().rows;
214
+
215
+ const { getTotalSize, getVirtualItems } = useVirtualizer({
216
+ getScrollElement,
217
+ count: rows.length,
218
+ overscan: 16,
219
+ estimateSize: () => 33,
220
+ });
221
+ const virtualRows = getVirtualItems();
222
+ const totalSize = getTotalSize();
223
+
224
+ const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
225
+ const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0;
237
226
 
238
- {debug && (
239
- <pre className='font-mono text-xs text-neutral-500 my-4 p-2 ring'>
240
- <code>{JSON.stringify(table.getState(), undefined, 2)}</code>
241
- </pre>
227
+ return (
228
+ <>
229
+ {paddingTop > 0 && (
230
+ <tbody role='none'>
231
+ <tr role='none'>
232
+ <td style={{ height: `${paddingTop}px` }} role='none' />
233
+ </tr>
234
+ </tbody>
242
235
  )}
243
- </TableProvider>
236
+ <TableBody rows={virtualRows.map((virtualRow) => rows[virtualRow.index])} />
237
+ {paddingBottom > 0 && (
238
+ <tbody role='none'>
239
+ <tr role='none'>
240
+ <td style={{ height: `${paddingBottom}px` }} role='none' />
241
+ </tr>
242
+ </tbody>
243
+ )}
244
+ </>
245
+ );
246
+ };
247
+
248
+ const GroupedTableContent = () => {
249
+ const {
250
+ table: { getGroupedRowModel, getHeaderGroups, getState },
251
+ debug,
252
+ } = useTableContext('GroupedTableContent');
253
+ return (
254
+ <>
255
+ {getGroupedRowModel().rows.map((row, i) => {
256
+ return (
257
+ <Fragment key={i}>
258
+ {/* TODO(burdon): Customize group header renderer. */}
259
+ <thead>
260
+ <tr>
261
+ {debug && <th />}
262
+ <th
263
+ // TODO(burdon): Calculate row span.
264
+ colSpan={getHeaderGroups()[0].headers.length}
265
+ className={groupTh({})}
266
+ >
267
+ {getState().grouping[0]}[{String(row.getGroupingValue(getState().grouping[0]))}]
268
+ </th>
269
+ </tr>
270
+ </thead>
271
+ <TableBody rows={row.subRows} />
272
+ </Fragment>
273
+ );
274
+ })}
275
+ </>
244
276
  );
245
277
  };
@@ -10,18 +10,19 @@ import { useTableContext } from './TableContext';
10
10
  import { tbodyTr } from '../../theme';
11
11
  import { Cell } from '../Cell/Cell';
12
12
 
13
+ const TABLE_BODY_NAME = 'TableBody';
14
+
13
15
  type TableBodyProps<TData extends RowData> = {
14
16
  rows: Row<TData>[];
15
17
  };
16
18
 
17
- const TABLE_BODY_NAME = 'TableBody';
18
-
19
19
  const TableBody = <TData extends RowData>({ rows }: TableBodyProps<TData>) => {
20
- const tableContext = useTableContext<TData>(TABLE_BODY_NAME);
21
- const { table, keyAccessor, debug, expand, isGrid, onDatumClick, currentDatum } = tableContext;
20
+ const { table, keyAccessor, currentDatum, debug, expand, isGrid, onDatumClick } =
21
+ useTableContext<TData>(TABLE_BODY_NAME);
22
22
  const rowSelection = table.getState().rowSelection;
23
23
  const domAttributes = useArrowNavigationGroup({ axis: 'grid' });
24
24
  const canBeCurrent = !isGrid && !!onDatumClick;
25
+
25
26
  return (
26
27
  <tbody {...(isGrid && domAttributes)}>
27
28
  {rows.map((row) => {
@@ -8,14 +8,15 @@ import React from 'react';
8
8
  import { useTableContext } from './TableContext';
9
9
  import { tfootRoot, tfootTr, tfootTh } from '../../theme';
10
10
 
11
- export type TableFooterProps = {};
11
+ type TableFooterProps = {};
12
12
 
13
- const TABLE_FOOTER_NAME = 'TableFooter';
13
+ const TABLE_FOOT_NAME = 'TableFooter';
14
14
 
15
15
  const TableFooter = <TData extends RowData>(props: TableFooterProps) => {
16
- const tableContext = useTableContext<TData>(TABLE_FOOTER_NAME);
16
+ const tableContext = useTableContext<TData>(TABLE_FOOT_NAME);
17
17
  const { table, expand, debug } = tableContext;
18
18
  const footers = table.getFooterGroups();
19
+
19
20
  return (
20
21
  <tfoot className={tfootRoot(props)}>
21
22
  {footers.map((footerGroup) => (
@@ -37,6 +38,8 @@ const TableFooter = <TData extends RowData>(props: TableFooterProps) => {
37
38
  );
38
39
  };
39
40
 
40
- TableFooter.displayName = TABLE_FOOTER_NAME;
41
+ TableFooter.displayName = TABLE_FOOT_NAME;
41
42
 
42
43
  export { TableFooter };
44
+
45
+ export type { TableFooterProps };
@@ -10,7 +10,7 @@ import { mx } from '@dxos/react-ui-theme';
10
10
  import { useTableContext } from './TableContext';
11
11
  import { theadResizeRoot, theadResizeThumb, theadRoot, theadTh, theadTr } from '../../theme';
12
12
 
13
- const TABLE_HEAD_NAME = 'TableHead';
13
+ const TABLE_HEAD_NAME = 'TableHeader';
14
14
 
15
15
  type TableHeadProps = {};
16
16
 
@@ -18,6 +18,7 @@ const TableHead = <TData extends RowData>(_props: TableHeadProps) => {
18
18
  const tableContext = useTableContext<TData>(TABLE_HEAD_NAME);
19
19
  const headerGroups = tableContext.table.getHeaderGroups();
20
20
  const state = tableContext.table.getState();
21
+
21
22
  return (
22
23
  <thead className={theadRoot(tableContext)}>
23
24
  {headerGroups.map((headerGroup) => {
@@ -64,6 +65,7 @@ const TableHead = <TData extends RowData>(_props: TableHeadProps) => {
64
65
  </th>
65
66
  );
66
67
  })}
68
+
67
69
  {tableContext.expand && <th />}
68
70
  </tr>
69
71
  );