@dxos/react-ui-table 0.4.8-main.3a03fb7 → 0.4.8-main.4a229ac
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/lib/browser/index.mjs +276 -191
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/Cell/Cell.d.ts.map +1 -1
- package/dist/types/src/components/ColumnMenu.d.ts.map +1 -1
- package/dist/types/src/components/ColumnMenu.stories.d.ts +10 -1
- package/dist/types/src/components/ColumnMenu.stories.d.ts.map +1 -1
- package/dist/types/src/components/SortButton.d.ts +8 -0
- package/dist/types/src/components/SortButton.d.ts.map +1 -0
- package/dist/types/src/components/Table/Table.d.ts +2 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableBody.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableHead.d.ts.map +1 -1
- package/dist/types/src/components/Table/props.d.ts +1 -0
- package/dist/types/src/components/Table/props.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts.map +1 -1
- package/dist/types/src/theme.d.ts +1 -1
- package/dist/types/src/theme.d.ts.map +1 -1
- package/package.json +15 -15
- package/src/components/Cell/Cell.tsx +4 -1
- package/src/components/ColumnMenu.stories.tsx +13 -6
- package/src/components/ColumnMenu.tsx +13 -4
- package/src/components/SortButton.tsx +34 -0
- package/src/components/Table/Table.stories.tsx +51 -62
- package/src/components/Table/Table.tsx +64 -41
- package/src/components/Table/TableBody.tsx +1 -0
- package/src/components/Table/TableHead.tsx +4 -2
- package/src/components/Table/props.ts +1 -0
- package/src/helpers.tsx +61 -45
- package/src/theme.ts +3 -1
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
|
|
7
7
|
import { Plugs, PlugsConnected } from '@phosphor-icons/react';
|
|
8
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
8
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import * as E from '@dxos/echo-schema/schema';
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
12
12
|
import { faker } from '@dxos/random';
|
|
13
|
-
import {
|
|
13
|
+
import { AnchoredOverflow, DensityProvider } from '@dxos/react-ui';
|
|
14
14
|
import { withTheme } from '@dxos/storybook-utils';
|
|
15
15
|
import { range } from '@dxos/util';
|
|
16
16
|
|
|
@@ -72,7 +72,7 @@ const tableStorySelectModel: SearchListQueryModel<Item> = {
|
|
|
72
72
|
|
|
73
73
|
const { helper, builder } = createColumnBuilder<Item>();
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const makeColumns = (onUpdate?: ValueUpdater<Item, any>): TableColumnDef<Item, any>[] => [
|
|
76
76
|
helper.display(builder.selectRow()),
|
|
77
77
|
helper.accessor((item) => item.publicKey, { id: 'key', ...builder.key({ tooltip: true }) }),
|
|
78
78
|
helper.accessor(
|
|
@@ -90,26 +90,14 @@ const columns = (onUpdate?: ValueUpdater<Item, any>): TableColumnDef<Item, any>[
|
|
|
90
90
|
builder.number({
|
|
91
91
|
label: 'Count',
|
|
92
92
|
meta: { resizable: true },
|
|
93
|
-
// TODO(burdon): Sorting.
|
|
94
|
-
getGroupingValue: (row) => (row.count ? (row.count < 2_000 ? 'A' : row.count < 5_000 ? 'B' : 'C') : 'D'),
|
|
95
|
-
}),
|
|
96
|
-
),
|
|
97
|
-
helper.accessor(
|
|
98
|
-
'company',
|
|
99
|
-
builder.combobox({
|
|
100
|
-
label: 'Company',
|
|
101
|
-
model: tableStorySelectModel,
|
|
102
93
|
onUpdate,
|
|
94
|
+
getGroupingValue: (row) => (row.count ? (row.count < 2000 ? 'A' : row.count < 5000 ? 'B' : 'C') : 'D'),
|
|
103
95
|
}),
|
|
104
96
|
),
|
|
97
|
+
helper.accessor('company', builder.combobox({ label: 'Company', model: tableStorySelectModel, onUpdate })),
|
|
105
98
|
helper.accessor(
|
|
106
99
|
'complete',
|
|
107
|
-
builder.switch({
|
|
108
|
-
// enableGrouping: true,
|
|
109
|
-
getGroupingValue: (row) => row.complete === true,
|
|
110
|
-
label: '',
|
|
111
|
-
onUpdate,
|
|
112
|
-
}),
|
|
100
|
+
builder.switch({ getGroupingValue: (row) => row.complete === true, label: '', onUpdate }),
|
|
113
101
|
),
|
|
114
102
|
helper.accessor('complete', builder.icon({ id: 'done', label: '' })),
|
|
115
103
|
helper.accessor(
|
|
@@ -135,18 +123,10 @@ export default {
|
|
|
135
123
|
keyAccessor: (item: Item) => item.publicKey.toHex(),
|
|
136
124
|
},
|
|
137
125
|
argTypes: {
|
|
138
|
-
header: {
|
|
139
|
-
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
control: 'boolean',
|
|
143
|
-
},
|
|
144
|
-
border: {
|
|
145
|
-
control: 'boolean',
|
|
146
|
-
},
|
|
147
|
-
fullWidth: {
|
|
148
|
-
control: 'boolean',
|
|
149
|
-
},
|
|
126
|
+
header: { control: 'boolean' },
|
|
127
|
+
footer: { control: 'boolean' },
|
|
128
|
+
border: { control: 'boolean' },
|
|
129
|
+
fullWidth: { control: 'boolean' },
|
|
150
130
|
grouping: {
|
|
151
131
|
control: 'select',
|
|
152
132
|
options: ['none', 'complete', 'count'],
|
|
@@ -164,10 +144,7 @@ export default {
|
|
|
164
144
|
limited: { key: false, started: false },
|
|
165
145
|
},
|
|
166
146
|
},
|
|
167
|
-
rowsSelectable: {
|
|
168
|
-
control: 'select',
|
|
169
|
-
options: [false, true, 'multi'],
|
|
170
|
-
},
|
|
147
|
+
rowsSelectable: { control: 'select', options: [false, true, 'multi'] },
|
|
171
148
|
},
|
|
172
149
|
decorators: [
|
|
173
150
|
withTheme,
|
|
@@ -181,21 +158,21 @@ export default {
|
|
|
181
158
|
|
|
182
159
|
export const Default = {
|
|
183
160
|
args: {
|
|
184
|
-
columns:
|
|
161
|
+
columns: makeColumns(),
|
|
185
162
|
data: createItems(20),
|
|
186
163
|
},
|
|
187
164
|
};
|
|
188
165
|
|
|
189
166
|
export const Scrolling = {
|
|
190
167
|
args: {
|
|
191
|
-
columns:
|
|
168
|
+
columns: makeColumns(),
|
|
192
169
|
data: createItems(200),
|
|
193
170
|
},
|
|
194
171
|
};
|
|
195
172
|
|
|
196
173
|
export const Visibility = {
|
|
197
174
|
args: {
|
|
198
|
-
columns:
|
|
175
|
+
columns: makeColumns(),
|
|
199
176
|
data: createItems(10),
|
|
200
177
|
columnVisibility: { key: false, started: false },
|
|
201
178
|
},
|
|
@@ -203,7 +180,7 @@ export const Visibility = {
|
|
|
203
180
|
|
|
204
181
|
export const Empty = {
|
|
205
182
|
args: {
|
|
206
|
-
columns:
|
|
183
|
+
columns: makeColumns(),
|
|
207
184
|
},
|
|
208
185
|
};
|
|
209
186
|
|
|
@@ -211,6 +188,7 @@ export const Dynamic = {
|
|
|
211
188
|
render: () => {
|
|
212
189
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
213
190
|
const [items, setItems] = useState<Item[]>(createItems(50));
|
|
191
|
+
|
|
214
192
|
useEffect(() => {
|
|
215
193
|
const interval = setInterval(() => {
|
|
216
194
|
setItems((items) => {
|
|
@@ -223,12 +201,14 @@ export const Dynamic = {
|
|
|
223
201
|
return () => clearInterval(interval);
|
|
224
202
|
}, []);
|
|
225
203
|
|
|
204
|
+
const columns = useMemo(() => makeColumns(), []);
|
|
205
|
+
|
|
226
206
|
return (
|
|
227
207
|
<AnchoredOverflow.Root classNames='max-bs-[80dvh]' ref={containerRef}>
|
|
228
208
|
<Table<Item>
|
|
229
209
|
rowsSelectable='multi'
|
|
230
210
|
keyAccessor={(row) => row.publicKey.toHex()}
|
|
231
|
-
columns={columns
|
|
211
|
+
columns={columns}
|
|
232
212
|
data={items}
|
|
233
213
|
fullWidth
|
|
234
214
|
footer
|
|
@@ -245,9 +225,13 @@ export const Editable = {
|
|
|
245
225
|
render: () => {
|
|
246
226
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
247
227
|
const [items, setItems] = useState<Item[]>(createItems(200));
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
228
|
+
|
|
229
|
+
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
230
|
+
(item, prop, value) => setItems((items) => updateItems(items, item.publicKey, prop, value)),
|
|
231
|
+
[setItems],
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
|
|
251
235
|
|
|
252
236
|
return (
|
|
253
237
|
<div ref={containerRef} className='fixed inset-0 overflow-auto'>
|
|
@@ -255,7 +239,7 @@ export const Editable = {
|
|
|
255
239
|
role='grid'
|
|
256
240
|
rowsSelectable='multi'
|
|
257
241
|
keyAccessor={(row) => row.publicKey.toHex()}
|
|
258
|
-
columns={columns
|
|
242
|
+
columns={columns}
|
|
259
243
|
data={items}
|
|
260
244
|
fullWidth
|
|
261
245
|
stickyHeader
|
|
@@ -269,25 +253,27 @@ export const Editable = {
|
|
|
269
253
|
|
|
270
254
|
export const Resizable = {
|
|
271
255
|
render: () => {
|
|
256
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
272
257
|
const [items, setItems] = useState<Item[]>(createItems(10));
|
|
273
|
-
const onUpdate: ValueUpdater<Item, any> = (item, prop, value) => {
|
|
274
|
-
setItems((items) => updateItems(items, item.publicKey, prop, value));
|
|
275
|
-
};
|
|
276
258
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
259
|
+
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
260
|
+
(item, prop, value) => setItems((items) => updateItems(items, item.publicKey, prop, value)),
|
|
261
|
+
[setItems],
|
|
262
|
+
);
|
|
280
263
|
|
|
281
264
|
return (
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
265
|
+
<div ref={containerRef} className='fixed inset-0 overflow-auto'>
|
|
266
|
+
<Table<Item>
|
|
267
|
+
rowsSelectable='multi'
|
|
268
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
269
|
+
columns={makeColumns(onUpdate)}
|
|
270
|
+
data={items}
|
|
271
|
+
fullWidth
|
|
272
|
+
stickyHeader
|
|
273
|
+
getScrollElement={() => containerRef.current}
|
|
274
|
+
// onColumnResize={handleColumnResize}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
291
277
|
);
|
|
292
278
|
},
|
|
293
279
|
};
|
|
@@ -297,9 +283,12 @@ export const TenThousandRows = {
|
|
|
297
283
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
298
284
|
const [items, setItems] = useState<Item[]>(createItems(10000));
|
|
299
285
|
|
|
300
|
-
const onUpdate: ValueUpdater<Item, any> = (
|
|
301
|
-
setItems((items) => updateItems(items, item.publicKey, prop, value))
|
|
302
|
-
|
|
286
|
+
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
287
|
+
(item, prop, value) => setItems((items) => updateItems(items, item.publicKey, prop, value)),
|
|
288
|
+
[setItems],
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
|
|
303
292
|
|
|
304
293
|
return (
|
|
305
294
|
<div ref={containerRef} className='fixed inset-0 overflow-auto'>
|
|
@@ -307,7 +296,7 @@ export const TenThousandRows = {
|
|
|
307
296
|
role='grid'
|
|
308
297
|
rowsSelectable='multi'
|
|
309
298
|
keyAccessor={(row) => row.publicKey.toHex()}
|
|
310
|
-
columns={columns
|
|
299
|
+
columns={columns}
|
|
311
300
|
data={items}
|
|
312
301
|
fullWidth
|
|
313
302
|
stickyHeader
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
5
5
|
import {
|
|
6
6
|
getCoreRowModel,
|
|
7
|
+
getSortedRowModel,
|
|
7
8
|
useReactTable,
|
|
8
9
|
type ColumnSizingInfoState,
|
|
9
10
|
type GroupingState,
|
|
@@ -12,12 +13,14 @@ import {
|
|
|
12
13
|
type OnChangeFn,
|
|
13
14
|
type RowSelectionState,
|
|
14
15
|
getGroupedRowModel,
|
|
16
|
+
getExpandedRowModel,
|
|
15
17
|
} from '@tanstack/react-table';
|
|
16
18
|
import { useVirtualizer, type VirtualizerOptions } from '@tanstack/react-virtual';
|
|
17
19
|
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
|
18
20
|
|
|
19
21
|
import { debounce } from '@dxos/async';
|
|
20
22
|
import { log } from '@dxos/log';
|
|
23
|
+
import { useDefaultValue } from '@dxos/react-ui';
|
|
21
24
|
|
|
22
25
|
import { TableBody } from './TableBody';
|
|
23
26
|
import { TableProvider as UntypedTableProvider, type TypedTableProvider, useTableContext } from './TableContext';
|
|
@@ -29,8 +32,6 @@ import { groupTh, tableRoot } from '../../theme';
|
|
|
29
32
|
export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
30
33
|
const {
|
|
31
34
|
role,
|
|
32
|
-
data = [],
|
|
33
|
-
columns = [],
|
|
34
35
|
onColumnResize,
|
|
35
36
|
columnVisibility,
|
|
36
37
|
header = true,
|
|
@@ -38,18 +39,17 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
38
39
|
debug,
|
|
39
40
|
onDataSelectionChange,
|
|
40
41
|
getScrollElement,
|
|
42
|
+
pinLastRow,
|
|
41
43
|
} = props;
|
|
42
44
|
|
|
43
|
-
const
|
|
45
|
+
const columns = useDefaultValue(props.columns, []);
|
|
46
|
+
const data = useDefaultValue(props.data, []);
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
// Column resizing
|
|
47
|
-
//
|
|
48
|
+
const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
|
|
48
49
|
|
|
49
50
|
const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
|
|
50
51
|
|
|
51
52
|
useEffect(() => {
|
|
52
|
-
// Set initial state.
|
|
53
53
|
setColumnSizing(
|
|
54
54
|
columns
|
|
55
55
|
.filter((column) => !!column.size && (column as any).prop !== undefined)
|
|
@@ -58,31 +58,16 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
58
58
|
return state;
|
|
59
59
|
}, {}),
|
|
60
60
|
);
|
|
61
|
-
}, [columns]);
|
|
61
|
+
}, [columns, setColumnSizing]);
|
|
62
62
|
|
|
63
63
|
const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
|
|
64
64
|
|
|
65
|
-
const onColumnResizeDebounced = onColumnResize && debounce(onColumnResize, 1_000);
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (columnSizingInfo.columnSizingStart?.length === 0) {
|
|
68
|
-
onColumnResizeDebounced?.(table.getState().columnSizing);
|
|
69
|
-
}
|
|
70
|
-
}, [columnSizingInfo]);
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
// Row selection
|
|
74
|
-
//
|
|
75
|
-
|
|
76
65
|
const [rowSelection = {}, setRowSelection] = useControllableState({
|
|
77
66
|
prop: props.rowSelection,
|
|
78
67
|
onChange: props.onRowSelectionChange,
|
|
79
68
|
defaultProp: props.defaultRowSelection,
|
|
80
69
|
});
|
|
81
70
|
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
onDataSelectionChange?.(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
|
|
84
|
-
}, [onDataSelectionChange, rowSelection]);
|
|
85
|
-
|
|
86
71
|
// TODO(thure): Does @tanstack/react-table really need this intervention? It did seem necessary to enforce single-selection...
|
|
87
72
|
const handleRowSelectionChange = useCallback<OnChangeFn<RowSelectionState>>(
|
|
88
73
|
(updaterOrValue) => {
|
|
@@ -96,15 +81,11 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
96
81
|
setRowSelection({});
|
|
97
82
|
}
|
|
98
83
|
},
|
|
99
|
-
[
|
|
84
|
+
[rowsSelectable, setRowSelection, rowSelection],
|
|
100
85
|
);
|
|
101
86
|
|
|
102
|
-
//
|
|
103
|
-
// Row grouping
|
|
104
|
-
//
|
|
105
|
-
|
|
106
87
|
const [grouping, handleGroupingChange] = useState<GroupingState>(props.grouping ?? []);
|
|
107
|
-
useEffect(() => handleGroupingChange(props.grouping ?? []), [props.grouping]);
|
|
88
|
+
useEffect(() => handleGroupingChange(props.grouping ?? []), [handleGroupingChange, props.grouping]);
|
|
108
89
|
|
|
109
90
|
const table = useReactTable({
|
|
110
91
|
// Data
|
|
@@ -135,23 +116,50 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
135
116
|
|
|
136
117
|
// Rows
|
|
137
118
|
getCoreRowModel: getCoreRowModel(),
|
|
119
|
+
getSortedRowModel: getSortedRowModel(),
|
|
120
|
+
getExpandedRowModel: getExpandedRowModel(),
|
|
138
121
|
|
|
139
122
|
// Grouping
|
|
140
123
|
getGroupedRowModel: grouping.length > 1 ? getGroupedRowModel() : undefined,
|
|
141
124
|
onGroupingChange: handleGroupingChange,
|
|
142
125
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: rowsSelectable
|
|
147
|
-
? { enableRowSelection: true }
|
|
148
|
-
: {}),
|
|
126
|
+
enableMultiRowSelection: rowsSelectable === 'multi' ? true : undefined,
|
|
127
|
+
enableRowSelection: rowsSelectable === true ? true : undefined,
|
|
128
|
+
|
|
149
129
|
onRowSelectionChange: handleRowSelectionChange,
|
|
150
130
|
|
|
131
|
+
enableSorting: true,
|
|
132
|
+
enableColumnPinning: true,
|
|
133
|
+
|
|
151
134
|
// Debug
|
|
152
135
|
debugTable: debug,
|
|
153
136
|
});
|
|
154
137
|
|
|
138
|
+
const onColumnResizeDebounced = onColumnResize && debounce(onColumnResize, 1_000);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
onDataSelectionChange?.(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
|
|
142
|
+
}, [onDataSelectionChange, rowSelection, table]);
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
// TODO(zan): This is super jank, let's think of a nicer way to do this ... later.
|
|
146
|
+
if (pinLastRow) {
|
|
147
|
+
// Clear row pinning
|
|
148
|
+
table.resetRowPinning();
|
|
149
|
+
|
|
150
|
+
const rows = table.getRowModel().rows;
|
|
151
|
+
rows[rows.length - 1].pin('bottom');
|
|
152
|
+
}
|
|
153
|
+
}, [pinLastRow, table, data]);
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const shouldTriggerResize = columnSizingInfo.columnSizingStart?.length === 0;
|
|
157
|
+
|
|
158
|
+
if (shouldTriggerResize) {
|
|
159
|
+
onColumnResizeDebounced?.(table.getState().columnSizing);
|
|
160
|
+
}
|
|
161
|
+
}, [columnSizingInfo, onColumnResizeDebounced, table]);
|
|
162
|
+
|
|
155
163
|
// Create additional expansion column if all columns have fixed width.
|
|
156
164
|
const expand = false; // columns.map((column) => column.size).filter(Boolean).length === columns?.length;
|
|
157
165
|
|
|
@@ -178,7 +186,11 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
178
186
|
const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
179
187
|
const { debug, classNames, getScrollElement, role, footer, grouping, fullWidth } = props;
|
|
180
188
|
const { table } = useTableContext<TData>('TableImpl');
|
|
181
|
-
|
|
189
|
+
|
|
190
|
+
const centerRows = table.getCenterRows();
|
|
191
|
+
const bottomRows = table.getBottomRows();
|
|
192
|
+
|
|
193
|
+
const rows = [...centerRows, ...bottomRows];
|
|
182
194
|
|
|
183
195
|
if (debug) {
|
|
184
196
|
return (
|
|
@@ -188,6 +200,8 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
188
200
|
);
|
|
189
201
|
}
|
|
190
202
|
|
|
203
|
+
const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn;
|
|
204
|
+
|
|
191
205
|
return (
|
|
192
206
|
<table
|
|
193
207
|
role={role}
|
|
@@ -198,7 +212,11 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
198
212
|
|
|
199
213
|
{grouping?.length !== 0 ? (
|
|
200
214
|
getScrollElement ? (
|
|
201
|
-
|
|
215
|
+
isResizingColumn ? (
|
|
216
|
+
<MemoizedVirtualisedTableContent getScrollElement={getScrollElement} />
|
|
217
|
+
) : (
|
|
218
|
+
<VirtualizedTableContent getScrollElement={getScrollElement} />
|
|
219
|
+
)
|
|
202
220
|
) : (
|
|
203
221
|
<TableBody rows={rows} />
|
|
204
222
|
)
|
|
@@ -214,10 +232,12 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
214
232
|
const VirtualizedTableContent = ({
|
|
215
233
|
getScrollElement,
|
|
216
234
|
}: Pick<VirtualizerOptions<Element, Element>, 'getScrollElement'>) => {
|
|
217
|
-
const {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
235
|
+
const { table } = useTableContext('VirtualizedTableContent');
|
|
236
|
+
|
|
237
|
+
const centerRows = table.getCenterRows();
|
|
238
|
+
const pinnedRows = table.getBottomRows();
|
|
239
|
+
|
|
240
|
+
const rows = [...centerRows, ...pinnedRows];
|
|
221
241
|
|
|
222
242
|
const { getTotalSize, getVirtualItems } = useVirtualizer({
|
|
223
243
|
getScrollElement,
|
|
@@ -225,6 +245,7 @@ const VirtualizedTableContent = ({
|
|
|
225
245
|
overscan: 4,
|
|
226
246
|
estimateSize: () => 33,
|
|
227
247
|
});
|
|
248
|
+
|
|
228
249
|
const virtualRows = getVirtualItems();
|
|
229
250
|
const totalSize = getTotalSize();
|
|
230
251
|
|
|
@@ -252,6 +273,8 @@ const VirtualizedTableContent = ({
|
|
|
252
273
|
);
|
|
253
274
|
};
|
|
254
275
|
|
|
276
|
+
export const MemoizedVirtualisedTableContent = React.memo(VirtualizedTableContent) as typeof VirtualizedTableContent;
|
|
277
|
+
|
|
255
278
|
const GroupedTableContent = () => {
|
|
256
279
|
const {
|
|
257
280
|
table: { getGroupedRowModel, getHeaderGroups, getState },
|
|
@@ -19,6 +19,7 @@ type TableBodyProps<TData extends RowData> = {
|
|
|
19
19
|
const TableBody = <TData extends RowData>({ rows }: TableBodyProps<TData>) => {
|
|
20
20
|
const { table, keyAccessor, currentDatum, debug, expand, isGrid, onDatumClick } =
|
|
21
21
|
useTableContext<TData>(TABLE_BODY_NAME);
|
|
22
|
+
|
|
22
23
|
const rowSelection = table.getState().rowSelection;
|
|
23
24
|
const domAttributes = useArrowNavigationGroup({ axis: 'grid' });
|
|
24
25
|
const canBeCurrent = !isGrid && !!onDatumClick;
|
|
@@ -34,6 +34,8 @@ const TableHead = <TData extends RowData>(_props: TableHeadProps) => {
|
|
|
34
34
|
|
|
35
35
|
{headerGroup.headers.map((header) => {
|
|
36
36
|
const isResizing = header.column.getIsResizing();
|
|
37
|
+
const resizeHandler = header.getResizeHandler();
|
|
38
|
+
|
|
37
39
|
return (
|
|
38
40
|
<th
|
|
39
41
|
key={header.id}
|
|
@@ -56,8 +58,8 @@ const TableHead = <TData extends RowData>(_props: TableHeadProps) => {
|
|
|
56
58
|
style={{
|
|
57
59
|
transform: `translateX(${isResizing ? state.columnSizingInfo.deltaOffset : 0}px)`,
|
|
58
60
|
}}
|
|
59
|
-
onMouseDown={
|
|
60
|
-
onTouchStart={
|
|
61
|
+
onMouseDown={resizeHandler}
|
|
62
|
+
onTouchStart={resizeHandler}
|
|
61
63
|
>
|
|
62
64
|
<div className={mx(theadResizeThumb(tableContext))} />
|
|
63
65
|
</div>
|
package/src/helpers.tsx
CHANGED
|
@@ -95,8 +95,11 @@ const ComboboxBuilderCell = <TData extends RowData>(cellContext: CellContext<TDa
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
const StringBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, string>) => {
|
|
98
|
-
const
|
|
99
|
-
const {
|
|
98
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
99
|
+
const { findPrevFocusable } = useFocusFinders();
|
|
100
|
+
|
|
101
|
+
const { onUpdate, cell } = cellContext.column.columnDef.meta as ColumnMeta<TData, string>;
|
|
102
|
+
|
|
100
103
|
// https://tanstack.com/table/v8/docs/examples/react/editable-data
|
|
101
104
|
const initialValue = cellContext.getValue();
|
|
102
105
|
const [value, setValue] = useState(() => {
|
|
@@ -109,32 +112,34 @@ const StringBuilderCell = <TData extends RowData>(cellContext: CellContext<TData
|
|
|
109
112
|
});
|
|
110
113
|
|
|
111
114
|
const handleSave = () => {
|
|
112
|
-
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
+
ref.current && document.activeElement === ref.current && findPrevFocusable(ref.current)?.focus();
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
if (value !== initialValue) {
|
|
118
|
+
onUpdate?.(cellContext.row.original, cellContext.column.id, value);
|
|
119
|
+
}
|
|
117
120
|
};
|
|
118
121
|
|
|
119
|
-
const handleCancel = () =>
|
|
120
|
-
setValue(initialValue);
|
|
121
|
-
};
|
|
122
|
+
const handleCancel = () => setValue(initialValue);
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
const rows = cellContext.table.getRowModel().flatRows;
|
|
124
|
+
let placeholder = false;
|
|
125
125
|
const columns = cellContext.table.getVisibleFlatColumns();
|
|
126
|
-
|
|
126
|
+
|
|
127
|
+
// Calling getRowModel is expensive, so only do it when we're a cell in the first position
|
|
128
|
+
if (columns[0].id === cellContext.column.id) {
|
|
129
|
+
const rowCount = cellContext.table.getRowModel().rows.length;
|
|
130
|
+
placeholder = cellContext.row.index === rowCount - 1 && columns[0].id === cellContext.column.id;
|
|
131
|
+
}
|
|
127
132
|
|
|
128
133
|
// TODO(burdon): Don't render inputs unless mouse over (Show ellipsis when div)?
|
|
129
134
|
return (
|
|
130
135
|
<Input.Root>
|
|
131
136
|
<Input.TextInput
|
|
132
|
-
ref={
|
|
137
|
+
ref={ref}
|
|
133
138
|
variant='subdued'
|
|
134
139
|
placeholder={placeholder ? 'Add row...' : undefined}
|
|
135
|
-
classNames={['is-full', textPadding]}
|
|
140
|
+
classNames={['is-full', textPadding, cell?.classNames]}
|
|
136
141
|
value={value ?? ''}
|
|
137
|
-
onBlur={
|
|
142
|
+
onBlur={handleSave}
|
|
138
143
|
onChange={(event) => setValue(event.target.value)}
|
|
139
144
|
onKeyDown={(event) => (event.key === 'Enter' && handleSave()) || (event.key === 'Escape' && handleCancel())}
|
|
140
145
|
/>
|
|
@@ -164,17 +169,20 @@ const NumberBuilderCell = <TData extends RowData>(cellContext: CellContext<TData
|
|
|
164
169
|
(!document.activeElement || document.activeElement !== ref.current) && setText(displayNumber(value, digits));
|
|
165
170
|
}, [digits, value]);
|
|
166
171
|
|
|
167
|
-
const handleEdit = () =>
|
|
168
|
-
if (onUpdate) {
|
|
169
|
-
setText(value !== undefined ? String(value) : '');
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
+
const handleEdit = () => setText(value !== undefined ? String(value) : '');
|
|
172
173
|
|
|
173
174
|
// TODO(burdon): Property is encoded as float (e.g., 6.1 => 6.099)
|
|
174
175
|
const handleSave = () => {
|
|
175
176
|
const nextValue = parseNumber(text);
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
|
|
178
|
+
if (!isNaN(nextValue)) {
|
|
179
|
+
onUpdate?.(cellContext.row.original, cellContext.column.id, nextValue);
|
|
180
|
+
setText(displayNumber(nextValue, digits));
|
|
181
|
+
} else {
|
|
182
|
+
// Reset the text to display the original value without updating the model.
|
|
183
|
+
setText(displayNumber(value, digits));
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
// Shift focus only if input is focused on save (e.g. on `Enter`).
|
|
179
187
|
ref.current && document.activeElement === ref.current && findPrevFocusable(ref.current)?.focus();
|
|
180
188
|
};
|
|
@@ -210,20 +218,23 @@ const RowSelectorBuilderCell = <TData extends RowData>(cellContext: CellContext<
|
|
|
210
218
|
const checked = row.getCanSelect()
|
|
211
219
|
? row.getIsSelected()
|
|
212
220
|
: row.getCanSelectSubRows() && (row.getIsSomeSelected() ? 'indeterminate' : row.getIsAllSubRowsSelected());
|
|
221
|
+
|
|
213
222
|
return (
|
|
214
|
-
<
|
|
215
|
-
<Input.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
row.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
<div className='flex justify-center w-full h-full' role='presentation'>
|
|
224
|
+
<Input.Root>
|
|
225
|
+
<Input.Checkbox
|
|
226
|
+
size={4}
|
|
227
|
+
classNames={[cell?.classNames]}
|
|
228
|
+
checked={checked}
|
|
229
|
+
onCheckedChange={(event) => {
|
|
230
|
+
if (row.getCanSelect()) {
|
|
231
|
+
row.getToggleSelectedHandler()(event);
|
|
232
|
+
}
|
|
233
|
+
}}
|
|
234
|
+
disabled={!(row.getCanSelect() || row.getCanSelectSubRows())}
|
|
235
|
+
/>
|
|
236
|
+
</Input.Root>
|
|
237
|
+
</div>
|
|
227
238
|
);
|
|
228
239
|
};
|
|
229
240
|
|
|
@@ -422,21 +433,22 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
422
433
|
return {
|
|
423
434
|
id,
|
|
424
435
|
size: 32,
|
|
425
|
-
|
|
426
|
-
maxSize: 32,
|
|
436
|
+
enableResizing: false,
|
|
427
437
|
meta: { onUpdate, cell: { classNames } },
|
|
428
438
|
header: ({ table }) => {
|
|
429
439
|
const { rowsSelectable } = useTableContext('HELPER_SELECT_ROW_HEADER_CELL');
|
|
430
440
|
const checked = table.getIsSomeRowsSelected() ? 'indeterminate' : table.getIsAllRowsSelected();
|
|
431
441
|
return rowsSelectable === 'multi' ? (
|
|
432
|
-
<
|
|
433
|
-
<Input.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
442
|
+
<div className='flex justify-center w-full h-full' role='presentation'>
|
|
443
|
+
<Input.Root>
|
|
444
|
+
<Input.Checkbox
|
|
445
|
+
size={4}
|
|
446
|
+
classNames={['mli-auto', classNames]}
|
|
447
|
+
checked={checked}
|
|
448
|
+
onCheckedChange={() => table.toggleAllRowsSelected()}
|
|
449
|
+
/>
|
|
450
|
+
</Input.Root>
|
|
451
|
+
</div>
|
|
440
452
|
) : null;
|
|
441
453
|
},
|
|
442
454
|
cell: RowSelectorBuilderCell,
|
|
@@ -453,6 +465,8 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
453
465
|
size: 50,
|
|
454
466
|
minSize: 50,
|
|
455
467
|
header: (column) => label ?? column.header.id,
|
|
468
|
+
// TODO(Zan): Implement sort algorithm
|
|
469
|
+
enableSorting: true,
|
|
456
470
|
cell: SwitchBuilderCell,
|
|
457
471
|
...props,
|
|
458
472
|
meta: { ...props.meta, onUpdate, cell: { ...props.meta?.cell, classNames: [textPadding, classNames] } },
|
|
@@ -467,6 +481,8 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
467
481
|
const IconOff = off?.Icon ?? X;
|
|
468
482
|
return {
|
|
469
483
|
size: size ?? 32,
|
|
484
|
+
// TODO(Zan): Implement sort algorithm
|
|
485
|
+
enableSorting: false,
|
|
470
486
|
header: (column) => <div className={'justify-center'}>{label ?? column.header.id}</div>,
|
|
471
487
|
cell: (cell) => {
|
|
472
488
|
const value = cell.getValue();
|