@tanstack/react-table 8.0.0-alpha.1 → 8.0.0-alpha.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/react-table",
3
3
  "author": "Tanner Linsley",
4
- "version": "8.0.0-alpha.1",
4
+ "version": "8.0.0-alpha.2",
5
5
  "description": "Hooks for building lightweight, fast and extendable datagrids for React",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/tanstack/react-table#readme",
package/src/core.tsx CHANGED
@@ -38,6 +38,7 @@ import * as Filters from './features/Filters'
38
38
  import * as Sorting from './features/Sorting'
39
39
  import * as Grouping from './features/Grouping'
40
40
  import * as Expanding from './features/Expanding'
41
+ import * as ColumnSizing from './features/ColumnSizing'
41
42
  import { RowModel } from '.'
42
43
 
43
44
  // import './features/withColumnResizing';
@@ -225,6 +226,10 @@ export type TableCore<TData, TValue, TFilterFns, TSortingFns, TAggregationFns> =
225
226
  columnId: string,
226
227
  userProps?: TGetter
227
228
  ) => undefined | PropGetterValue<CellProps, TGetter>
229
+ getTableWidth: () => number
230
+ getLeftTableWidth: () => number
231
+ getCenterTableWidth: () => number
232
+ getRightTableWidth: () => number
228
233
  }
229
234
 
230
235
  export type CoreRow<TData, TValue, TFilterFns, TSortingFns, TAggregationFns> = {
@@ -410,6 +415,7 @@ export function createTableInstance<
410
415
  ...Sorting.getDefaultOptions(instance),
411
416
  ...Grouping.getDefaultOptions(instance),
412
417
  ...Expanding.getDefaultOptions(instance),
418
+ ...ColumnSizing.getDefaultOptions(instance),
413
419
  }
414
420
 
415
421
  const defaultState = {}
@@ -432,6 +438,7 @@ export function createTableInstance<
432
438
  ...Sorting.getInitialState(),
433
439
  ...Grouping.getInitialState(),
434
440
  ...Expanding.getInitialState(),
441
+ ...ColumnSizing.getInitialState(),
435
442
  ...(options.initialState ?? {}),
436
443
  }
437
444
 
@@ -451,6 +458,7 @@ export function createTableInstance<
451
458
  ...Sorting.getInstance(instance),
452
459
  ...Grouping.getInstance(instance),
453
460
  ...Expanding.getInstance(instance),
461
+ ...ColumnSizing.getInstance(instance),
454
462
  rerender,
455
463
  initialState,
456
464
  internalState: initialState,
@@ -563,9 +571,7 @@ export function createTableInstance<
563
571
  TSortingFns,
564
572
  TAggregationFns
565
573
  > = {
566
- width: 150,
567
- minWidth: 20,
568
- maxWidth: Number.MAX_SAFE_INTEGER,
574
+ ...ColumnSizing.defaultColumnSizing,
569
575
  ...defaultColumn,
570
576
  ...columnDef,
571
577
  id: `${id}`,
@@ -669,6 +675,16 @@ export function createTableInstance<
669
675
  TAggregationFns
670
676
  >,
671
677
  instance
678
+ ),
679
+ ColumnSizing.createColumn(
680
+ column as Column<
681
+ TData,
682
+ TValue,
683
+ TFilterFns,
684
+ TSortingFns,
685
+ TAggregationFns
686
+ >,
687
+ instance
672
688
  )
673
689
  )
674
690
 
@@ -783,9 +799,14 @@ export function createTableInstance<
783
799
  throw new Error()
784
800
  }
785
801
 
802
+ const columnSize = instance.getState().columnSizing[column.id]
803
+
786
804
  return Math.min(
787
- Math.max(column.minWidth ?? 0, column.width ?? 0),
788
- column.maxWidth ?? 0
805
+ Math.max(
806
+ column.minWidth ?? ColumnSizing.defaultColumnSizing.minWidth,
807
+ columnSize ?? column.width ?? ColumnSizing.defaultColumnSizing.width
808
+ ),
809
+ column.maxWidth ?? ColumnSizing.defaultColumnSizing.maxWidth
789
810
  )
790
811
  },
791
812
 
@@ -1188,6 +1209,23 @@ export function createTableInstance<
1188
1209
  userProps
1189
1210
  )
1190
1211
  },
1212
+
1213
+ getTableWidth: () =>
1214
+ instance.getHeaderGroups()[0]?.headers.reduce((sum, header) => {
1215
+ return sum + header.getWidth()
1216
+ }, 0) ?? 0,
1217
+ getLeftTableWidth: () =>
1218
+ instance.getLeftHeaderGroups()[0]?.headers.reduce((sum, header) => {
1219
+ return sum + header.getWidth()
1220
+ }, 0) ?? 0,
1221
+ getCenterTableWidth: () =>
1222
+ instance.getCenterHeaderGroups()[0]?.headers.reduce((sum, header) => {
1223
+ return sum + header.getWidth()
1224
+ }, 0) ?? 0,
1225
+ getRightTableWidth: () =>
1226
+ instance.getRightHeaderGroups()[0]?.headers.reduce((sum, header) => {
1227
+ return sum + header.getWidth()
1228
+ }, 0) ?? 0,
1191
1229
  }
1192
1230
 
1193
1231
  return Object.assign(instance, finalInstance)
@@ -5,7 +5,7 @@ import { ReactTable, ColumnDef, AccessorFn, Options } from './types'
5
5
  import { Overwrite } from './utils'
6
6
 
7
7
  type TableHelper<TData, TValue, TFilterFns, TSortingFns, TAggregationFns> = {
8
- RowType: <TTData>() => TableHelper<
8
+ RowType<TTData>(): TableHelper<
9
9
  TTData,
10
10
  TValue,
11
11
  TFilterFns,
@@ -0,0 +1,482 @@
1
+ import React, {
2
+ ComponentProps,
3
+ MouseEvent as ReactMouseEvent,
4
+ PropsWithoutRef,
5
+ PropsWithRef,
6
+ TouchEvent as ReactTouchEvent,
7
+ } from 'react'
8
+ import {
9
+ Column,
10
+ Getter,
11
+ Header,
12
+ OnChangeFn,
13
+ PropGetterValue,
14
+ ReactTable,
15
+ Updater,
16
+ } from '../types'
17
+ import { functionalUpdate, makeStateUpdater, memo, propGetter } from '../utils'
18
+
19
+ //
20
+
21
+ export type ColumnSizing = Record<string, number>
22
+
23
+ export type ColumnSizingInfoState = {
24
+ startOffset: null | number
25
+ startSize: null | number
26
+ deltaOffset: null | number
27
+ deltaPercentage: null | number
28
+ isResizingColumn: false | string
29
+ columnSizingStart: [string, number][]
30
+ }
31
+
32
+ export type ColumnSizingTableState = {
33
+ columnSizing: ColumnSizing
34
+ columnSizingInfo: ColumnSizingInfoState
35
+ }
36
+
37
+ export type ColumnResizeMode = 'onChange' | 'onEnd'
38
+
39
+ export type ColumnSizingOptions = {
40
+ enableColumnResizing?: boolean
41
+ columnResizeMode?: ColumnResizeMode
42
+ onColumnSizingChange?: OnChangeFn<ColumnSizing>
43
+ onColumnSizingInfoChange?: OnChangeFn<ColumnSizingInfoState>
44
+ }
45
+
46
+ export type ColumnSizingDefaultOptions = {
47
+ columnResizeMode: ColumnResizeMode
48
+ onColumnSizingChange: OnChangeFn<ColumnSizing>
49
+ onColumnSizingInfoChange: OnChangeFn<ColumnSizingInfoState>
50
+ }
51
+
52
+ export type HeaderResizerProps = {
53
+ title?: string
54
+ onMouseDown?: (e: ReactMouseEvent) => void
55
+ onTouchStart?: (e: ReactTouchEvent) => void
56
+ draggable?: boolean
57
+ role?: string
58
+ }
59
+
60
+ export type ColumnSizingInstance<
61
+ TData,
62
+ TValue,
63
+ TFilterFns,
64
+ TSortingFns,
65
+ TAggregationFns
66
+ > = {
67
+ setColumnSizing: (updater: Updater<ColumnSizing>) => void
68
+ setColumnSizingInfo: (updater: Updater<ColumnSizingInfoState>) => void
69
+ resetColumnSizing: () => void
70
+ resetColumnSize: (columnId: string) => void
71
+ resetHeaderSize: (headerId: string) => void
72
+ resetHeaderSizeInfo: () => void
73
+ getColumnCanResize: (columnId: string) => boolean
74
+ getHeaderCanResize: (headerId: string) => boolean
75
+ getHeaderResizerProps: <TGetter extends Getter<HeaderResizerProps>>(
76
+ columnId: string,
77
+ userProps?: TGetter
78
+ ) => undefined | PropGetterValue<HeaderResizerProps, TGetter>
79
+ getColumnIsResizing: (columnId: string) => boolean
80
+ getHeaderIsResizing: (headerId: string) => boolean
81
+ }
82
+
83
+ export type ColumnSizingColumnDef = {
84
+ enableResizing?: boolean
85
+ defaultCanResize?: boolean
86
+ }
87
+
88
+ export type ColumnSizingColumn<
89
+ TData,
90
+ TValue,
91
+ TFilterFns,
92
+ TSortingFns,
93
+ TAggregationFns
94
+ > = {
95
+ getCanResize: () => boolean
96
+ getIsResizing: () => boolean
97
+ resetSize: () => void
98
+ }
99
+
100
+ export type ColumnSizingHeader<
101
+ TData,
102
+ TValue,
103
+ TFilterFns,
104
+ TSortingFns,
105
+ TAggregationFns
106
+ > = {
107
+ getCanResize: () => boolean
108
+ getIsResizing: () => boolean
109
+ getResizerProps: <TGetter extends Getter<HeaderResizerProps>>(
110
+ userProps?: TGetter
111
+ ) => undefined | PropGetterValue<HeaderResizerProps, TGetter>
112
+ resetSize: () => void
113
+ }
114
+
115
+ //
116
+
117
+ export const defaultColumnSizing = {
118
+ width: 150,
119
+ minWidth: 20,
120
+ maxWidth: Number.MAX_SAFE_INTEGER,
121
+ }
122
+
123
+ export function getInitialState(): ColumnSizingTableState {
124
+ return {
125
+ columnSizing: {},
126
+ columnSizingInfo: {
127
+ startOffset: null,
128
+ startSize: null,
129
+ deltaOffset: null,
130
+ deltaPercentage: null,
131
+ isResizingColumn: false,
132
+ columnSizingStart: [],
133
+ },
134
+ }
135
+ }
136
+
137
+ export function getDefaultOptions<
138
+ TData,
139
+ TValue,
140
+ TFilterFns,
141
+ TSortingFns,
142
+ TAggregationFns
143
+ >(
144
+ instance: ReactTable<TData, TValue, TFilterFns, TSortingFns, TAggregationFns>
145
+ ): ColumnSizingDefaultOptions {
146
+ return {
147
+ columnResizeMode: 'onEnd',
148
+ onColumnSizingChange: makeStateUpdater('columnSizing', instance),
149
+ onColumnSizingInfoChange: makeStateUpdater('columnSizingInfo', instance),
150
+ }
151
+ }
152
+
153
+ export function getInstance<
154
+ TData,
155
+ TValue,
156
+ TFilterFns,
157
+ TSortingFns,
158
+ TAggregationFns
159
+ >(
160
+ instance: ReactTable<TData, TValue, TFilterFns, TSortingFns, TAggregationFns>
161
+ ): ColumnSizingInstance<
162
+ TData,
163
+ TValue,
164
+ TFilterFns,
165
+ TSortingFns,
166
+ TAggregationFns
167
+ > {
168
+ return {
169
+ setColumnSizing: updater =>
170
+ instance.options.onColumnSizingChange?.(
171
+ updater,
172
+ functionalUpdate(updater, instance.getState().columnSizing)
173
+ ),
174
+ setColumnSizingInfo: updater =>
175
+ instance.options.onColumnSizingInfoChange?.(
176
+ updater,
177
+ functionalUpdate(updater, instance.getState().columnSizingInfo)
178
+ ),
179
+ resetColumnSizing: () => {
180
+ instance.setColumnSizing(instance.initialState.columnSizing ?? {})
181
+ },
182
+ resetHeaderSizeInfo: () => {
183
+ instance.setColumnSizingInfo(instance.initialState.columnSizingInfo ?? {})
184
+ },
185
+ resetColumnSize: columnId => {
186
+ instance.setColumnSizing(({ [columnId]: _, ...rest }) => {
187
+ return rest
188
+ })
189
+ },
190
+ resetHeaderSize: headerId => {
191
+ const header = instance.getHeader(headerId)
192
+
193
+ if (!header) {
194
+ return
195
+ }
196
+
197
+ return instance.resetColumnSize(header.column.id)
198
+ },
199
+ getHeaderCanResize: headerId => {
200
+ const header = instance.getHeader(headerId)
201
+
202
+ if (!header) {
203
+ throw new Error()
204
+ }
205
+
206
+ return instance.getColumnCanResize(header.column.id)
207
+ },
208
+ getColumnCanResize: columnId => {
209
+ const column = instance.getColumn(columnId)
210
+
211
+ if (!column) {
212
+ throw new Error()
213
+ }
214
+
215
+ return (
216
+ column.enableResizing ??
217
+ instance.options.enableColumnResizing ??
218
+ column.defaultCanResize ??
219
+ true
220
+ )
221
+ },
222
+ getColumnIsResizing: columnId => {
223
+ const column = instance.getColumn(columnId)
224
+
225
+ if (!column) {
226
+ throw new Error()
227
+ }
228
+
229
+ return instance.getState().columnSizingInfo.isResizingColumn === columnId
230
+ },
231
+ getHeaderIsResizing: headerId => {
232
+ const header = instance.getHeader(headerId)
233
+
234
+ if (!header) {
235
+ throw new Error()
236
+ }
237
+
238
+ return instance.getColumnIsResizing(header.column.id)
239
+ },
240
+ getHeaderResizerProps: (headerId, userProps) => {
241
+ const header = instance.getHeader(headerId)
242
+
243
+ if (!header) {
244
+ return
245
+ }
246
+
247
+ const column = instance.getColumn(header.column.id)
248
+
249
+ if (!column) {
250
+ return
251
+ }
252
+
253
+ const canResize = column.getCanResize()
254
+
255
+ const onResizeStart = (e: ReactMouseEvent | ReactTouchEvent) => {
256
+ if (isTouchStartEvent(e)) {
257
+ // lets not respond to multiple touches (e.g. 2 or 3 fingers)
258
+ if (e.touches && e.touches.length > 1) {
259
+ return
260
+ }
261
+ }
262
+
263
+ const columnSizingStart: [string, number][] = header
264
+ .getLeafHeaders()
265
+ .map(d => [d.column.id, d.getWidth()])
266
+
267
+ const clientX = isTouchStartEvent(e)
268
+ ? Math.round(e.touches[0].clientX)
269
+ : e.clientX
270
+
271
+ const updateOffset = (
272
+ eventType: 'move' | 'end',
273
+ clientXPos?: number
274
+ ) => {
275
+ if (typeof clientXPos !== 'number') {
276
+ return
277
+ }
278
+
279
+ let newColumnSizing: ColumnSizing = {}
280
+
281
+ instance.setColumnSizingInfo(old => {
282
+ const deltaOffset = clientXPos - (old?.startOffset ?? 0)
283
+ const deltaPercentage = Math.max(
284
+ deltaOffset / (old?.startSize ?? 0),
285
+ -0.999999
286
+ )
287
+
288
+ old.columnSizingStart.forEach(([columnId, headerWidth]) => {
289
+ newColumnSizing[columnId] = Math.max(
290
+ headerWidth + headerWidth * deltaPercentage,
291
+ 0
292
+ )
293
+ })
294
+
295
+ return {
296
+ ...old,
297
+ deltaOffset,
298
+ deltaPercentage,
299
+ }
300
+ })
301
+
302
+ if (
303
+ instance.options.columnResizeMode === 'onChange' ||
304
+ eventType === 'end'
305
+ ) {
306
+ instance.setColumnSizing(old => ({
307
+ ...old,
308
+ ...newColumnSizing,
309
+ }))
310
+ }
311
+ }
312
+
313
+ const onMove = (clientXPos?: number) => updateOffset('move', clientXPos)
314
+
315
+ const onEnd = (clientXPos?: number) => {
316
+ updateOffset('end', clientXPos)
317
+
318
+ instance.setColumnSizingInfo(old => ({
319
+ ...old,
320
+ isResizingColumn: false,
321
+ startOffset: null,
322
+ startSize: null,
323
+ deltaOffset: null,
324
+ deltaPercentage: null,
325
+ columnSizingStart: [],
326
+ }))
327
+ }
328
+
329
+ const mouseEvents = {
330
+ moveHandler: (e: MouseEvent) => onMove(e.clientX),
331
+ upHandler: (e: MouseEvent) => {
332
+ document.removeEventListener('mousemove', mouseEvents.moveHandler)
333
+ document.removeEventListener('mouseup', mouseEvents.upHandler)
334
+ onEnd(e.clientX)
335
+ },
336
+ }
337
+
338
+ const touchEvents = {
339
+ moveHandler: (e: TouchEvent) => {
340
+ if (e.cancelable) {
341
+ e.preventDefault()
342
+ e.stopPropagation()
343
+ }
344
+ onMove(e.touches[0].clientX)
345
+ return false
346
+ },
347
+ upHandler: (e: TouchEvent) => {
348
+ document.removeEventListener('touchmove', touchEvents.moveHandler)
349
+ document.removeEventListener('touchend', touchEvents.upHandler)
350
+ if (e.cancelable) {
351
+ e.preventDefault()
352
+ e.stopPropagation()
353
+ }
354
+ onEnd(e.touches[0].clientX)
355
+ },
356
+ }
357
+
358
+ const passiveIfSupported = passiveEventSupported()
359
+ ? { passive: false }
360
+ : false
361
+
362
+ if (isTouchStartEvent(e)) {
363
+ document.addEventListener(
364
+ 'touchmove',
365
+ touchEvents.moveHandler,
366
+ passiveIfSupported
367
+ )
368
+ document.addEventListener(
369
+ 'touchend',
370
+ touchEvents.upHandler,
371
+ passiveIfSupported
372
+ )
373
+ } else {
374
+ document.addEventListener(
375
+ 'mousemove',
376
+ mouseEvents.moveHandler,
377
+ passiveIfSupported
378
+ )
379
+ document.addEventListener(
380
+ 'mouseup',
381
+ mouseEvents.upHandler,
382
+ passiveIfSupported
383
+ )
384
+ }
385
+
386
+ instance.setColumnSizingInfo(old => ({
387
+ ...old,
388
+ startOffset: clientX,
389
+ startSize: header.getWidth(),
390
+ deltaOffset: 0,
391
+ deltaPercentage: 0,
392
+ columnSizingStart,
393
+ isResizingColumn: header.column.id,
394
+ }))
395
+ }
396
+
397
+ const initialProps: HeaderResizerProps = canResize
398
+ ? {
399
+ title: 'Toggle Grouping',
400
+ draggable: false,
401
+ role: 'separator',
402
+ onMouseDown: (e: ReactMouseEvent) => {
403
+ e.persist()
404
+ onResizeStart(e)
405
+ },
406
+ onTouchStart: (e: ReactTouchEvent) => {
407
+ e.persist()
408
+ onResizeStart(e)
409
+ },
410
+ }
411
+ : {}
412
+
413
+ return propGetter(initialProps, userProps)
414
+ },
415
+ }
416
+ }
417
+
418
+ export function createColumn<
419
+ TData,
420
+ TValue,
421
+ TFilterFns,
422
+ TSortingFns,
423
+ TAggregationFns
424
+ >(
425
+ column: Column<TData, TValue, TFilterFns, TSortingFns, TAggregationFns>,
426
+ instance: ReactTable<TData, TValue, TFilterFns, TSortingFns, TAggregationFns>
427
+ ): ColumnSizingColumn<TData, TValue, TFilterFns, TSortingFns, TAggregationFns> {
428
+ return {
429
+ getIsResizing: () => instance.getColumnIsResizing(column.id),
430
+ getCanResize: () => instance.getColumnCanResize(column.id),
431
+ resetSize: () => instance.resetColumnSize(column.id),
432
+ }
433
+ }
434
+
435
+ export function createHeader<
436
+ TData,
437
+ TValue,
438
+ TFilterFns,
439
+ TSortingFns,
440
+ TAggregationFns
441
+ >(
442
+ header: Header<TData, TValue, TFilterFns, TSortingFns, TAggregationFns>,
443
+ instance: ReactTable<TData, TValue, TFilterFns, TSortingFns, TAggregationFns>
444
+ ): ColumnSizingHeader<TData, TValue, TFilterFns, TSortingFns, TAggregationFns> {
445
+ return {
446
+ getIsResizing: () => instance.getHeaderIsResizing(header.id),
447
+ getCanResize: () => instance.getHeaderCanResize(header.id),
448
+ getResizerProps: userProps =>
449
+ instance.getHeaderResizerProps(header.id, userProps),
450
+ resetSize: () => instance.resetHeaderSize(header.id),
451
+ }
452
+ }
453
+
454
+ let passiveSupported: boolean | null = null
455
+ export function passiveEventSupported() {
456
+ if (typeof passiveSupported === 'boolean') return passiveSupported
457
+
458
+ let supported = false
459
+ try {
460
+ const options = {
461
+ get passive() {
462
+ supported = true
463
+ return false
464
+ },
465
+ }
466
+
467
+ const noop = () => {}
468
+
469
+ window.addEventListener('test', noop, options)
470
+ window.removeEventListener('test', noop)
471
+ } catch (err) {
472
+ supported = false
473
+ }
474
+ passiveSupported = supported
475
+ return passiveSupported
476
+ }
477
+
478
+ function isTouchStartEvent(
479
+ e: ReactTouchEvent | ReactMouseEvent
480
+ ): e is ReactTouchEvent {
481
+ return e.type === 'touchstart'
482
+ }
@@ -438,7 +438,7 @@ export function getInstance<
438
438
  setColumnFilters: (updater: Updater<ColumnFiltersState>) => {
439
439
  const leafColumns = instance.getAllLeafColumns()
440
440
 
441
- const updateFn = (old?: ColumnFiltersState) => {
441
+ const updateFn = (old: ColumnFiltersState) => {
442
442
  return functionalUpdate(updater, old)?.filter(filter => {
443
443
  const column = leafColumns.find(d => d.id === filter.id)
444
444