Skip to content

Commit de1a715

Browse files
authoredFeb 14, 2024··
feat: new column pinning, sizing, ordering APIs for easier sticky pinning (#5344)
* improved memo method to accept depArgs getMemoOptions method to reduce memo boilerplate new ColumnSizing getAfter API (similar to getStart) added memoization to column.getStart method new Ordering column.getIndex API new Ordering column.getIsFirstColumn API new Ordering column.getIsLastColumn API improved table._getPinnedRows memo performance * update column pinning example and docs * update lock file * even better column sizing performance * smaller code
1 parent 3aa9da9 commit de1a715

38 files changed

+873
-360
lines changed
 

‎docs/api/features/column-ordering.md

+26
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,29 @@ resetColumnOrder: (defaultState?: boolean) => void
4242
```
4343
4444
Resets the **columnOrder** state to `initialState.columnOrder`, or `true` can be passed to force a default blank state reset to `[]`.
45+
46+
## Column API
47+
48+
### `getIndex`
49+
50+
```tsx
51+
getIndex: (position?: ColumnPinningPosition) => number
52+
```
53+
54+
Returns the index of the column in the order of the visible columns. Optionally pass a `position` parameter to get the index of the column in a sub-section of the table.
55+
56+
### `getIsFirstColumn`
57+
58+
```tsx
59+
getIsFirstColumn: (position?: ColumnPinningPosition) => boolean
60+
```
61+
62+
Returns `true` if the column is the first column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the first in a sub-section of the table.
63+
64+
### `getIsLastColumn`
65+
66+
```tsx
67+
getIsLastColumn: (position?: ColumnPinningPosition) => boolean
68+
```
69+
70+
Returns `true` if the column is the last column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the last in a sub-section of the table.

‎docs/api/features/column-sizing.md

+14-4
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ The maximum allowed size for the column
6161
6262
## Column API
6363
64-
## Table Options
65-
6664
### `getSize`
6765
6866
```tsx
@@ -77,7 +75,19 @@ Returns the current size of the column
7775
getStart: (position?: ColumnPinningPosition) => number
7876
```
7977
80-
Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the column.
78+
Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the column, measuring the size of all preceding columns.
79+
80+
Useful for sticky or absolute positioning of columns. (e.g. `left` or `transform`)
81+
82+
### `getAfter`
83+
84+
```tsx
85+
getAfter: (position?: ColumnPinningPosition) => number
86+
```
87+
88+
Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the column, measuring the size of all succeeding columns.
89+
90+
Useful for sticky or absolute positioning of columns. (e.g. `right` or `transform`)
8191
8292
### `getCanResize`
8393
@@ -134,7 +144,7 @@ Returns an event handler function that can be used to resize the header. It can
134144
135145
The dragging and release events are automatically handled for you.
136146
137-
## Table API Options
147+
## Table Options
138148
139149
### `enableColumnResizing`
140150

‎docs/config.json

+4
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@
252252
"to": "framework/react/examples/column-pinning",
253253
"label": "Column Pinning"
254254
},
255+
{
256+
"to": "framework/react/examples/column-pinning-sticky",
257+
"label": "Sticky Column Pinning"
258+
},
255259
{
256260
"to": "framework/react/examples/column-sizing",
257261
"label": "Column Sizing"

‎docs/guide/column-pinning.md

+71
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,87 @@ title: Column Pinning
77
Want to skip to the implementation? Check out these examples:
88

99
- [column-pinning](../framework/react/examples/column-pinning)
10+
- [sticky-column-pinning](../framework/react/examples/column-pinning/sticky)
1011
- [row-pinning](../framework/react/examples/row-pinning)
1112

13+
### Other Examples
14+
15+
- [Svelte column-pinning](../framework/svelte/examples/column-pinning)
16+
- [Vue column-pinning](../framework/vue/examples/column-pinning)
17+
1218
## API
1319

1420
[Pinning API](../api/features/pinning)
1521

1622
## Column Pinning Guide
1723

24+
TanStack Table offers state and APIs helpful for implementing column pinning features in your table UI. You can implement column pinning in multiple ways. You can either split pinned columns into their own separate tables, or you can keep all columns in the same table, but use the pinning state to order the columns correctly and use sticky CSS to pin the columns to the left or right.
25+
26+
### How Column Pinning Affects Column Order
27+
1828
There are 3 table features that can reorder columns, which happen in the following order:
1929

2030
1. **Column Pinning** - If pinning, columns are split into left, center (unpinned), and right pinned columns.
2131
2. Manual [Column Ordering](../guide/column-ordering) - A manually specified column order is applied.
2232
3. [Grouping](../guide/grouping) - If grouping is enabled, a grouping state is active, and `tableOptions.columnGroupingMode` is set to `'reorder' | 'remove'`, then the grouped columns are reordered to the start of the column flow.
33+
34+
The only way to change the order of the pinned columns is in the `columnPinning.left` and `columnPinning.right` state itself. `columnOrder` state will only affect the order of the unpinned ("center") columns.
35+
36+
### Column Pinning State
37+
38+
Managing the `columnPinning` state is optional, and usually not necessary unless you are adding persistent state features. TanStack Table will already keep track of the column pinning state for you. Manage the `columnPinning` state just like any other table state if you need to.
39+
40+
```jsx
41+
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>({
42+
left: [],
43+
right: [],
44+
});
45+
//...
46+
const table = useReactTable({
47+
//...
48+
state: {
49+
columnPinning,
50+
//...
51+
}
52+
onColumnPinningChange: setColumnPinning,
53+
//...
54+
});
55+
```
56+
57+
### Pin Columns by Default
58+
59+
A very common use case is to pin some columns by default. You can do this by either initializing the `columnPinning` state with the pinned columnIds, or by using the `initialState` table option
60+
61+
```jsx
62+
const table = useReactTable({
63+
//...
64+
initialState: {
65+
columnPinning: {
66+
left: ['expand-column'],
67+
right: ['actions-column'],
68+
},
69+
//...
70+
}
71+
//...
72+
});
73+
```
74+
75+
### Useful Column Pinning APIs
76+
77+
> Note: Some of these APIs are new in v8.12.0
78+
79+
There are a handful of useful Column API methods to help you implement column pinning features:
80+
81+
- [`column.getCanPin`](../api/features/pinning#getcanpin): Use to determine if a column can be pinned.
82+
- [`column.pin`](../api/features/pinning#pin): Use to pin a column to the left or right. Or use to unpin a column.
83+
- [`column.getIsPinned`](../api/features/pinning#getispinned): Use to determine where a column is pinned.
84+
- [`column.getStart`](../api/features/pinning#getstart): Use to provide the correct `left` CSS value for a pinned column.
85+
- [`column.getAfter`](../api/features/pinning#getafter): Use to provide the correct `right` CSS value for a pinned column.
86+
- [`column.getIsLastColumn`](../api/features/pinning#getislastcolumn): Use to determine if a column is the last column in its pinned group. Useful for adding a box-shadow
87+
- [`column.getIsFirstColumn`](../api/features/pinning#getisfirstcolumn): Use to determine if a column is the first column in its pinned group. Useful for adding a box-shadow
88+
89+
### Split Table Column Pinning
90+
91+
If you are just using sticky CSS to pin columns, you can for the most part, just render the table as you normally would with the `table.getHeaderGroups` and `row.getVisibleCells` methods.
92+
93+
However, if you are splitting up pinned columns into their own separate tables, you can make use of the `table.getLeftHeaderGroups`, `table.getCenterHeaderGroups`, `table.getRightHeaderGroups`, `row.getLeftVisibleCells`, `row.getCenterVisibleCells`, and `row.getRightVisibleCells` methods to only render the columns that are relevant to the current table.

‎docs/guide/virtualization.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Want to skip to the implementation? Check out these examples:
88

99
- [virtualized-columns](../framework/react/examples/virtualized-columns)
1010
- [virtualized-rows (dynamic row height)](../framework/react/examples/virtualized-rows)
11-
- [virtualized-rows (fixed row height)](../../../../virtual/v3/docs/examples/react/table)
11+
- [virtualized-rows (fixed row height)](../../../../virtual/v3/docs/framework/react/examples/table)
1212
- [virtualized-infinite-scrolling](../framework/react/examples/virtualized-infinite-scrolling)
1313

1414
## API

‎examples/react/column-dnd/src/main.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const defaultColumns: ColumnDef<Person>[] = [
3939
header: 'Age',
4040
footer: props => props.column.id,
4141
},
42-
4342
{
4443
accessorKey: 'visits',
4544
id: 'visits',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Example
2+
3+
To run this example:
4+
5+
- `npm install` or `yarn`
6+
- `npm run start` or `yarn start`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
<script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "tanstack-table-example-column-pinning-sticky",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"serve": "vite preview --port 3000",
9+
"start": "vite"
10+
},
11+
"dependencies": {
12+
"@faker-js/faker": "^8.3.1",
13+
"@tanstack/react-table": "^8.11.8",
14+
"react": "^18.2.0",
15+
"react-dom": "^18.2.0"
16+
},
17+
"devDependencies": {
18+
"@rollup/plugin-replace": "^5.0.5",
19+
"@types/react": "^18.2.48",
20+
"@types/react-dom": "^18.2.18",
21+
"@vitejs/plugin-react": "^4.2.1",
22+
"vite": "^5.0.11"
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
html {
2+
font-family: sans-serif;
3+
font-size: 14px;
4+
}
5+
6+
.table-container {
7+
border: 1px solid lightgray;
8+
overflow-x: scroll;
9+
width: 100%;
10+
max-width: 960px;
11+
position: relative;
12+
}
13+
14+
table {
15+
/* box-shadow and borders will not work with positon: sticky otherwise */
16+
border-collapse: separate !important;
17+
border-spacing: 0;
18+
}
19+
20+
th {
21+
background-color: lightgray;
22+
border-bottom: 1px solid lightgray;
23+
font-weight: bold;
24+
height: 30px;
25+
padding: 2px 4px;
26+
position: relative;
27+
text-align: center;
28+
}
29+
30+
td {
31+
background-color: white;
32+
padding: 2px 4px;
33+
}
34+
35+
.resizer {
36+
background: rgba(0, 0, 0, 0.5);
37+
cursor: col-resize;
38+
height: 100%;
39+
position: absolute;
40+
right: 0;
41+
top: 0;
42+
touch-action: none;
43+
user-select: none;
44+
width: 5px;
45+
}
46+
47+
.resizer.isResizing {
48+
background: blue;
49+
opacity: 1;
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import React, { CSSProperties } from 'react'
2+
import ReactDOM from 'react-dom/client'
3+
4+
import './index.css'
5+
6+
import {
7+
Column,
8+
ColumnDef,
9+
flexRender,
10+
getCoreRowModel,
11+
useReactTable,
12+
} from '@tanstack/react-table'
13+
import { makeData, Person } from './makeData'
14+
import { faker } from '@faker-js/faker'
15+
16+
//These are the important styles to make sticky column pinning work!
17+
//Apply styles like this using your CSS strategy of choice with this kind of logic to head cells, data cells, footer cells, etc.
18+
//View the index.css file for more needed styles such as border-collapse: separate
19+
const getCommonPinningStyles = (column: Column<Person>): CSSProperties => {
20+
const isPinned = column.getIsPinned()
21+
const isLastLeftPinnedColumn =
22+
isPinned === 'left' && column.getIsLastColumn('left')
23+
const isFirstRightPinnedColumn =
24+
isPinned === 'right' && column.getIsFirstColumn('right')
25+
26+
return {
27+
boxShadow: isLastLeftPinnedColumn
28+
? '-4px 0 4px -4px gray inset'
29+
: isFirstRightPinnedColumn
30+
? '4px 0 4px -4px gray inset'
31+
: undefined,
32+
left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined,
33+
right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined,
34+
opacity: isPinned ? 0.95 : 1,
35+
position: isPinned ? 'sticky' : 'relative',
36+
width: column.getSize(),
37+
zIndex: isPinned ? 1 : 0,
38+
}
39+
}
40+
41+
const defaultColumns: ColumnDef<Person>[] = [
42+
{
43+
accessorKey: 'firstName',
44+
id: 'firstName',
45+
header: 'First Name',
46+
cell: info => info.getValue(),
47+
footer: props => props.column.id,
48+
size: 180,
49+
},
50+
{
51+
accessorFn: row => row.lastName,
52+
id: 'lastName',
53+
cell: info => info.getValue(),
54+
header: () => <span>Last Name</span>,
55+
footer: props => props.column.id,
56+
size: 180,
57+
},
58+
{
59+
accessorKey: 'age',
60+
id: 'age',
61+
header: 'Age',
62+
footer: props => props.column.id,
63+
size: 180,
64+
},
65+
{
66+
accessorKey: 'visits',
67+
id: 'visits',
68+
header: 'Visits',
69+
footer: props => props.column.id,
70+
size: 180,
71+
},
72+
{
73+
accessorKey: 'status',
74+
id: 'status',
75+
header: 'Status',
76+
footer: props => props.column.id,
77+
size: 180,
78+
},
79+
{
80+
accessorKey: 'progress',
81+
id: 'progress',
82+
header: 'Profile Progress',
83+
footer: props => props.column.id,
84+
size: 180,
85+
},
86+
]
87+
88+
function App() {
89+
const [data, setData] = React.useState(() => makeData(30))
90+
const [columns] = React.useState(() => [...defaultColumns])
91+
92+
const rerender = () => setData(() => makeData(30))
93+
94+
const table = useReactTable({
95+
data,
96+
columns,
97+
getCoreRowModel: getCoreRowModel(),
98+
debugTable: true,
99+
debugHeaders: true,
100+
debugColumns: true,
101+
columnResizeMode: 'onChange',
102+
})
103+
104+
const randomizeColumns = () => {
105+
table.setColumnOrder(
106+
faker.helpers.shuffle(table.getAllLeafColumns().map(d => d.id))
107+
)
108+
}
109+
110+
return (
111+
<div className="p-2">
112+
<div className="inline-block border border-black shadow rounded">
113+
<div className="px-1 border-b border-black">
114+
<label>
115+
<input
116+
{...{
117+
type: 'checkbox',
118+
checked: table.getIsAllColumnsVisible(),
119+
onChange: table.getToggleAllColumnsVisibilityHandler(),
120+
}}
121+
/>{' '}
122+
Toggle All
123+
</label>
124+
</div>
125+
{table.getAllLeafColumns().map(column => {
126+
return (
127+
<div key={column.id} className="px-1">
128+
<label>
129+
<input
130+
{...{
131+
type: 'checkbox',
132+
checked: column.getIsVisible(),
133+
onChange: column.getToggleVisibilityHandler(),
134+
}}
135+
/>{' '}
136+
{column.id}
137+
</label>
138+
</div>
139+
)
140+
})}
141+
</div>
142+
<div className="h-4" />
143+
<div className="flex flex-wrap gap-2">
144+
<button onClick={() => rerender()} className="border p-1">
145+
Regenerate
146+
</button>
147+
<button onClick={() => randomizeColumns()} className="border p-1">
148+
Shuffle Columns
149+
</button>
150+
</div>
151+
<div className="h-4" />
152+
<div className="table-container">
153+
<table
154+
style={{
155+
width: table.getTotalSize(),
156+
}}
157+
>
158+
<thead>
159+
{table.getHeaderGroups().map(headerGroup => (
160+
<tr key={headerGroup.id}>
161+
{headerGroup.headers.map(header => {
162+
const { column } = header
163+
164+
return (
165+
<th
166+
key={header.id}
167+
colSpan={header.colSpan}
168+
//IMPORTANT: This is where the magic happens!
169+
style={{ ...getCommonPinningStyles(column) }}
170+
>
171+
<div className="whitespace-nowrap">
172+
{header.isPlaceholder
173+
? null
174+
: flexRender(
175+
header.column.columnDef.header,
176+
header.getContext()
177+
)}{' '}
178+
{/* Demo getIndex behavior */}
179+
{column.getIndex(column.getIsPinned() || 'center')}
180+
</div>
181+
{!header.isPlaceholder && header.column.getCanPin() && (
182+
<div className="flex gap-1 justify-center">
183+
{header.column.getIsPinned() !== 'left' ? (
184+
<button
185+
className="border rounded px-2"
186+
onClick={() => {
187+
header.column.pin('left')
188+
}}
189+
>
190+
{'<='}
191+
</button>
192+
) : null}
193+
{header.column.getIsPinned() ? (
194+
<button
195+
className="border rounded px-2"
196+
onClick={() => {
197+
header.column.pin(false)
198+
}}
199+
>
200+
X
201+
</button>
202+
) : null}
203+
{header.column.getIsPinned() !== 'right' ? (
204+
<button
205+
className="border rounded px-2"
206+
onClick={() => {
207+
header.column.pin('right')
208+
}}
209+
>
210+
{'=>'}
211+
</button>
212+
) : null}
213+
</div>
214+
)}
215+
<div
216+
{...{
217+
onDoubleClick: () => header.column.resetSize(),
218+
onMouseDown: header.getResizeHandler(),
219+
onTouchStart: header.getResizeHandler(),
220+
className: `resizer ${
221+
header.column.getIsResizing() ? 'isResizing' : ''
222+
}`,
223+
}}
224+
/>
225+
</th>
226+
)
227+
})}
228+
</tr>
229+
))}
230+
</thead>
231+
<tbody>
232+
{table.getRowModel().rows.map(row => (
233+
<tr key={row.id}>
234+
{row.getVisibleCells().map(cell => {
235+
const { column } = cell
236+
return (
237+
<td
238+
key={cell.id}
239+
//IMPORTANT: This is where the magic happens!
240+
style={{ ...getCommonPinningStyles(column) }}
241+
>
242+
{flexRender(
243+
cell.column.columnDef.cell,
244+
cell.getContext()
245+
)}
246+
</td>
247+
)
248+
})}
249+
</tr>
250+
))}
251+
</tbody>
252+
</table>
253+
</div>
254+
<pre>{JSON.stringify(table.getState().columnPinning, null, 2)}</pre>
255+
</div>
256+
)
257+
}
258+
259+
const rootElement = document.getElementById('root')
260+
if (!rootElement) throw new Error('Failed to find the root element')
261+
262+
ReactDOM.createRoot(rootElement).render(
263+
<React.StrictMode>
264+
<App />
265+
</React.StrictMode>
266+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { faker } from '@faker-js/faker'
2+
3+
export type Person = {
4+
firstName: string
5+
lastName: string
6+
age: number
7+
visits: number
8+
progress: number
9+
status: 'relationship' | 'complicated' | 'single'
10+
subRows?: Person[]
11+
}
12+
13+
const range = (len: number) => {
14+
const arr: number[] = []
15+
for (let i = 0; i < len; i++) {
16+
arr.push(i)
17+
}
18+
return arr
19+
}
20+
21+
const newPerson = (): Person => {
22+
return {
23+
firstName: faker.person.firstName(),
24+
lastName: faker.person.lastName(),
25+
age: faker.number.int(40),
26+
visits: faker.number.int(1000),
27+
progress: faker.number.int(100),
28+
status: faker.helpers.shuffle<Person['status']>([
29+
'relationship',
30+
'complicated',
31+
'single',
32+
])[0]!,
33+
}
34+
}
35+
36+
export function makeData(...lens: number[]) {
37+
const makeDataLevel = (depth = 0): Person[] => {
38+
const len = lens[depth]!
39+
return range(len).map((d): Person => {
40+
return {
41+
...newPerson(),
42+
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
43+
}
44+
})
45+
}
46+
47+
return makeDataLevel()
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"useDefineForClassFields": true,
5+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
6+
"module": "ESNext",
7+
"skipLibCheck": true,
8+
9+
/* Bundler mode */
10+
"moduleResolution": "bundler",
11+
"allowImportingTsExtensions": true,
12+
"resolveJsonModule": true,
13+
"isolatedModules": true,
14+
"noEmit": true,
15+
"jsx": "react-jsx",
16+
17+
/* Linting */
18+
"strict": true,
19+
"noUnusedLocals": true,
20+
"noUnusedParameters": true,
21+
"noFallthroughCasesInSwitch": true
22+
},
23+
"include": ["src"]
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineConfig } from 'vite'
2+
import react from '@vitejs/plugin-react'
3+
import rollupReplace from '@rollup/plugin-replace'
4+
5+
// https://vitejs.dev/config/
6+
export default defineConfig({
7+
plugins: [
8+
rollupReplace({
9+
preventAssignment: true,
10+
values: {
11+
__DEV__: JSON.stringify(true),
12+
'process.env.NODE_ENV': JSON.stringify('development'),
13+
},
14+
}),
15+
react(),
16+
],
17+
})

‎packages/table-core/src/core/cell.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RowData, Cell, Column, Row, Table } from '../types'
2-
import { Getter, memo } from '../utils'
2+
import { Getter, getMemoOptions, memo } from '../utils'
33

44
export interface CellContext<TData extends RowData, TValue> {
55
cell: Cell<TData, TValue>
@@ -74,10 +74,7 @@ export function createCell<TData extends RowData, TValue>(
7474
getValue: cell.getValue,
7575
renderValue: cell.renderValue,
7676
}),
77-
{
78-
key: process.env.NODE_ENV === 'development' && 'cell.getContext',
79-
debug: () => table.options.debugAll,
80-
}
77+
getMemoOptions(table.options, 'debugCells', 'cell.getContext')
8178
),
8279
}
8380

‎packages/table-core/src/core/column.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
RowData,
77
ColumnDefResolved,
88
} from '../types'
9-
import { memo } from '../utils'
9+
import { getMemoOptions, memo } from '../utils'
1010

1111
export interface CoreColumn<TData extends RowData, TValue> {
1212
/**
@@ -137,10 +137,7 @@ export function createColumn<TData extends RowData, TValue>(
137137
...column.columns?.flatMap(d => d.getFlatColumns()),
138138
]
139139
},
140-
{
141-
key: process.env.NODE_ENV === 'production' && 'column.getFlatColumns',
142-
debug: () => table.options.debugAll ?? table.options.debugColumns,
143-
}
140+
getMemoOptions(table.options, 'debugColumns', 'column.getFlatColumns')
144141
),
145142
getLeafColumns: memo(
146143
() => [table._getOrderColumnsFn()],
@@ -155,10 +152,7 @@ export function createColumn<TData extends RowData, TValue>(
155152

156153
return [column as Column<TData, TValue>]
157154
},
158-
{
159-
key: process.env.NODE_ENV === 'production' && 'column.getLeafColumns',
160-
debug: () => table.options.debugAll ?? table.options.debugColumns,
161-
}
155+
getMemoOptions(table.options, 'debugColumns', 'column.getLeafColumns')
162156
),
163157
}
164158

‎packages/table-core/src/core/headers.ts

+19-65
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { RowData, Column, Header, HeaderGroup, Table } from '../types'
2-
import { memo } from '../utils'
2+
import { getMemoOptions, memo } from '../utils'
33
import { TableFeature } from './table'
44

5+
const debug = 'debugHeaders'
6+
57
export interface CoreHeaderGroup<TData extends RowData> {
68
depth: number
79
headers: Header<TData, unknown>[]
@@ -288,10 +290,7 @@ export const Headers: TableFeature = {
288290

289291
return headerGroups
290292
},
291-
{
292-
key: process.env.NODE_ENV === 'development' && 'getHeaderGroups',
293-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
294-
}
293+
getMemoOptions(table.options, debug, 'getHeaderGroups')
295294
)
296295

297296
table.getCenterHeaderGroups = memo(
@@ -307,10 +306,7 @@ export const Headers: TableFeature = {
307306
)
308307
return buildHeaderGroups(allColumns, leafColumns, table, 'center')
309308
},
310-
{
311-
key: process.env.NODE_ENV === 'development' && 'getCenterHeaderGroups',
312-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
313-
}
309+
getMemoOptions(table.options, debug, 'getCenterHeaderGroups')
314310
)
315311

316312
table.getLeftHeaderGroups = memo(
@@ -327,10 +323,7 @@ export const Headers: TableFeature = {
327323

328324
return buildHeaderGroups(allColumns, orderedLeafColumns, table, 'left')
329325
},
330-
{
331-
key: process.env.NODE_ENV === 'development' && 'getLeftHeaderGroups',
332-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
333-
}
326+
getMemoOptions(table.options, debug, 'getLeftHeaderGroups')
334327
)
335328

336329
table.getRightHeaderGroups = memo(
@@ -347,10 +340,7 @@ export const Headers: TableFeature = {
347340

348341
return buildHeaderGroups(allColumns, orderedLeafColumns, table, 'right')
349342
},
350-
{
351-
key: process.env.NODE_ENV === 'development' && 'getRightHeaderGroups',
352-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
353-
}
343+
getMemoOptions(table.options, debug, 'getRightHeaderGroups')
354344
)
355345

356346
// Footer Groups
@@ -360,43 +350,31 @@ export const Headers: TableFeature = {
360350
headerGroups => {
361351
return [...headerGroups].reverse()
362352
},
363-
{
364-
key: process.env.NODE_ENV === 'development' && 'getFooterGroups',
365-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
366-
}
353+
getMemoOptions(table.options, debug, 'getFooterGroups')
367354
)
368355

369356
table.getLeftFooterGroups = memo(
370357
() => [table.getLeftHeaderGroups()],
371358
headerGroups => {
372359
return [...headerGroups].reverse()
373360
},
374-
{
375-
key: process.env.NODE_ENV === 'development' && 'getLeftFooterGroups',
376-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
377-
}
361+
getMemoOptions(table.options, debug, 'getLeftFooterGroups')
378362
)
379363

380364
table.getCenterFooterGroups = memo(
381365
() => [table.getCenterHeaderGroups()],
382366
headerGroups => {
383367
return [...headerGroups].reverse()
384368
},
385-
{
386-
key: process.env.NODE_ENV === 'development' && 'getCenterFooterGroups',
387-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
388-
}
369+
getMemoOptions(table.options, debug, 'getCenterFooterGroups')
389370
)
390371

391372
table.getRightFooterGroups = memo(
392373
() => [table.getRightHeaderGroups()],
393374
headerGroups => {
394375
return [...headerGroups].reverse()
395376
},
396-
{
397-
key: process.env.NODE_ENV === 'development' && 'getRightFooterGroups',
398-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
399-
}
377+
getMemoOptions(table.options, debug, 'getRightFooterGroups')
400378
)
401379

402380
// Flat Headers
@@ -410,10 +388,7 @@ export const Headers: TableFeature = {
410388
})
411389
.flat()
412390
},
413-
{
414-
key: process.env.NODE_ENV === 'development' && 'getFlatHeaders',
415-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
416-
}
391+
getMemoOptions(table.options, debug, 'getFlatHeaders')
417392
)
418393

419394
table.getLeftFlatHeaders = memo(
@@ -425,10 +400,7 @@ export const Headers: TableFeature = {
425400
})
426401
.flat()
427402
},
428-
{
429-
key: process.env.NODE_ENV === 'development' && 'getLeftFlatHeaders',
430-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
431-
}
403+
getMemoOptions(table.options, debug, 'getLeftFlatHeaders')
432404
)
433405

434406
table.getCenterFlatHeaders = memo(
@@ -440,10 +412,7 @@ export const Headers: TableFeature = {
440412
})
441413
.flat()
442414
},
443-
{
444-
key: process.env.NODE_ENV === 'development' && 'getCenterFlatHeaders',
445-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
446-
}
415+
getMemoOptions(table.options, debug, 'getCenterFlatHeaders')
447416
)
448417

449418
table.getRightFlatHeaders = memo(
@@ -455,10 +424,7 @@ export const Headers: TableFeature = {
455424
})
456425
.flat()
457426
},
458-
{
459-
key: process.env.NODE_ENV === 'development' && 'getRightFlatHeaders',
460-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
461-
}
427+
getMemoOptions(table.options, debug, 'getRightFlatHeaders')
462428
)
463429

464430
// Leaf Headers
@@ -468,32 +434,23 @@ export const Headers: TableFeature = {
468434
flatHeaders => {
469435
return flatHeaders.filter(header => !header.subHeaders?.length)
470436
},
471-
{
472-
key: process.env.NODE_ENV === 'development' && 'getCenterLeafHeaders',
473-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
474-
}
437+
getMemoOptions(table.options, debug, 'getCenterLeafHeaders')
475438
)
476439

477440
table.getLeftLeafHeaders = memo(
478441
() => [table.getLeftFlatHeaders()],
479442
flatHeaders => {
480443
return flatHeaders.filter(header => !header.subHeaders?.length)
481444
},
482-
{
483-
key: process.env.NODE_ENV === 'development' && 'getLeftLeafHeaders',
484-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
485-
}
445+
getMemoOptions(table.options, debug, 'getLeftLeafHeaders')
486446
)
487447

488448
table.getRightLeafHeaders = memo(
489449
() => [table.getRightFlatHeaders()],
490450
flatHeaders => {
491451
return flatHeaders.filter(header => !header.subHeaders?.length)
492452
},
493-
{
494-
key: process.env.NODE_ENV === 'development' && 'getRightLeafHeaders',
495-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
496-
}
453+
getMemoOptions(table.options, debug, 'getRightLeafHeaders')
497454
)
498455

499456
table.getLeafHeaders = memo(
@@ -513,10 +470,7 @@ export const Headers: TableFeature = {
513470
})
514471
.flat()
515472
},
516-
{
517-
key: process.env.NODE_ENV === 'development' && 'getLeafHeaders',
518-
debug: () => table.options.debugAll ?? table.options.debugHeaders,
519-
}
473+
getMemoOptions(table.options, debug, 'getLeafHeaders')
520474
)
521475
},
522476
}

‎packages/table-core/src/core/row.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RowData, Cell, Row, Table } from '../types'
2-
import { flattenBy, memo } from '../utils'
2+
import { flattenBy, getMemoOptions, memo } from '../utils'
33
import { createCell } from './cell'
44

55
export interface CoreRow<TData extends RowData> {
@@ -174,10 +174,7 @@ export const createRow = <TData extends RowData>(
174174
return createCell(table, row as Row<TData>, column, column.id)
175175
})
176176
},
177-
{
178-
key: process.env.NODE_ENV === 'development' && 'row.getAllCells',
179-
debug: () => table.options.debugAll ?? table.options.debugRows,
180-
}
177+
getMemoOptions(table.options, 'debugRows', 'getAllCells')
181178
),
182179

183180
_getAllCellsByColumnId: memo(
@@ -191,11 +188,7 @@ export const createRow = <TData extends RowData>(
191188
{} as Record<string, Cell<TData, unknown>>
192189
)
193190
},
194-
{
195-
key:
196-
process.env.NODE_ENV === 'production' && 'row.getAllCellsByColumnId',
197-
debug: () => table.options.debugAll ?? table.options.debugRows,
198-
}
191+
getMemoOptions(table.options, 'debugRows', 'getAllCellsByColumnId')
199192
),
200193
}
201194

‎packages/table-core/src/core/table.ts

+12-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { functionalUpdate, memo, RequiredKeys } from '../utils'
1+
import { functionalUpdate, getMemoOptions, memo, RequiredKeys } from '../utils'
22

33
import {
44
Updater,
@@ -87,6 +87,12 @@ export interface CoreOptions<TData extends RowData> {
8787
* @link [Guide](https://tanstack.com/table/v8/docs/guide/tables)
8888
*/
8989
debugAll?: boolean
90+
/**
91+
* Set this option to `true` to output cell debugging information to the console.
92+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/core/table#debugcells]
93+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/tables)
94+
*/
95+
debugCells?: boolean
9096
/**
9197
* Set this option to `true` to output column debugging information to the console.
9298
* @link [API Docs](https://tanstack.com/table/v8/docs/api/core/table#debugcolumns)
@@ -422,10 +428,7 @@ export function createTable<TData extends RowData>(
422428
...defaultColumn,
423429
} as Partial<ColumnDef<TData, unknown>>
424430
},
425-
{
426-
debug: () => table.options.debugAll ?? table.options.debugColumns,
427-
key: process.env.NODE_ENV === 'development' && 'getDefaultColumnDef',
428-
}
431+
getMemoOptions(options, 'debugColumns', '_getDefaultColumnDef')
429432
),
430433

431434
_getColumnDefs: () => table.options.columns,
@@ -456,10 +459,7 @@ export function createTable<TData extends RowData>(
456459

457460
return recurseColumns(columnDefs)
458461
},
459-
{
460-
key: process.env.NODE_ENV === 'development' && 'getAllColumns',
461-
debug: () => table.options.debugAll ?? table.options.debugColumns,
462-
}
462+
getMemoOptions(options, 'debugColumns', 'getAllColumns')
463463
),
464464

465465
getAllFlatColumns: memo(
@@ -469,10 +469,7 @@ export function createTable<TData extends RowData>(
469469
return column.getFlatColumns()
470470
})
471471
},
472-
{
473-
key: process.env.NODE_ENV === 'development' && 'getAllFlatColumns',
474-
debug: () => table.options.debugAll ?? table.options.debugColumns,
475-
}
472+
getMemoOptions(options, 'debugColumns', 'getAllFlatColumns')
476473
),
477474

478475
_getAllFlatColumnsById: memo(
@@ -486,10 +483,7 @@ export function createTable<TData extends RowData>(
486483
{} as Record<string, Column<TData, unknown>>
487484
)
488485
},
489-
{
490-
key: process.env.NODE_ENV === 'development' && 'getAllFlatColumnsById',
491-
debug: () => table.options.debugAll ?? table.options.debugColumns,
492-
}
486+
getMemoOptions(options, 'debugColumns', 'getAllFlatColumnsById')
493487
),
494488

495489
getAllLeafColumns: memo(
@@ -498,10 +492,7 @@ export function createTable<TData extends RowData>(
498492
let leafColumns = allColumns.flatMap(column => column.getLeafColumns())
499493
return orderColumns(leafColumns)
500494
},
501-
{
502-
key: process.env.NODE_ENV === 'development' && 'getAllLeafColumns',
503-
debug: () => table.options.debugAll ?? table.options.debugColumns,
504-
}
495+
getMemoOptions(options, 'debugColumns', 'getAllLeafColumns')
505496
),
506497

507498
getColumn: columnId => {

‎packages/table-core/src/features/ColumnSizing.ts

+33-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { _getVisibleLeafColumns } from '..'
12
import { TableFeature } from '../core/table'
23
import { RowData, Column, Header, OnChangeFn, Table, Updater } from '../types'
3-
import { makeStateUpdater } from '../utils'
4+
import { getMemoOptions, makeStateUpdater, memo } from '../utils'
45
import { ColumnPinningPosition } from './Pinning'
56

67
//
@@ -164,11 +165,15 @@ export interface ColumnSizingColumn {
164165
*/
165166
getSize: () => number
166167
/**
167-
* Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all preceding headers.
168+
* Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all preceding (left) headers in relation to the current column.
168169
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-sizing#getstart)
169170
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-sizing)
170171
*/
171-
getStart: (position?: ColumnPinningPosition) => number
172+
getStart: (position?: ColumnPinningPosition | 'center') => number
173+
/**
174+
* Returns the offset measurement along the row-axis (usually the x-axis for standard tables) for the header. This is effectively a sum of the offset measurements of all succeeding (right) headers in relation to the current column.
175+
*/
176+
getAfter: (position?: ColumnPinningPosition | 'center') => number
172177
/**
173178
* Resets the column to its initial size.
174179
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-sizing#resetsize)
@@ -257,25 +262,33 @@ export const ColumnSizing: TableFeature = {
257262
column.columnDef.maxSize ?? defaultColumnSizing.maxSize
258263
)
259264
}
260-
column.getStart = position => {
261-
const columns = !position
262-
? table.getVisibleLeafColumns()
263-
: position === 'left'
264-
? table.getLeftVisibleLeafColumns()
265-
: table.getRightVisibleLeafColumns()
266-
267-
const index = columns.findIndex(d => d.id === column.id)
268265

269-
if (index > 0) {
270-
const prevSiblingColumn = columns[index - 1]!
266+
column.getStart = memo(
267+
position => [
268+
position,
269+
_getVisibleLeafColumns(table, position),
270+
table.getState().columnSizing,
271+
],
272+
(position, columns) =>
273+
columns
274+
.slice(0, column.getIndex(position))
275+
.reduce((sum, column) => sum + column.getSize(), 0),
276+
getMemoOptions(table.options, 'debugColumns', 'getStart')
277+
)
278+
279+
column.getAfter = memo(
280+
position => [
281+
position,
282+
_getVisibleLeafColumns(table, position),
283+
table.getState().columnSizing,
284+
],
285+
(position, columns) =>
286+
columns
287+
.slice(column.getIndex(position) + 1)
288+
.reduce((sum, column) => sum + column.getSize(), 0),
289+
getMemoOptions(table.options, 'debugColumns', 'getAfter')
290+
)
271291

272-
return (
273-
prevSiblingColumn.getStart(position) + prevSiblingColumn.getSize()
274-
)
275-
}
276-
277-
return 0
278-
}
279292
column.resetSize = () => {
280293
table.setColumnSizing(({ [column.id]: _, ...rest }) => {
281294
return rest

‎packages/table-core/src/features/Ordering.ts

+74-35
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { makeStateUpdater, memo } from '../utils'
1+
import { getMemoOptions, makeStateUpdater, memo } from '../utils'
22

33
import { Table, OnChangeFn, Updater, Column, RowData } from '../types'
44

55
import { orderColumns } from './Grouping'
66
import { TableFeature } from '../core/table'
7+
import { ColumnPinningPosition, _getVisibleLeafColumns } from '..'
78

89
export interface ColumnOrderTableState {
910
columnOrder: ColumnOrderState
@@ -20,6 +21,27 @@ export interface ColumnOrderOptions {
2021
onColumnOrderChange?: OnChangeFn<ColumnOrderState>
2122
}
2223

24+
export interface ColumnOrderColumn {
25+
/**
26+
* Returns the index of the column in the order of the visible columns. Optionally pass a `position` parameter to get the index of the column in a sub-section of the table
27+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-ordering#getindex)
28+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-ordering)
29+
*/
30+
getIndex: (position?: ColumnPinningPosition | 'center') => number
31+
/**
32+
* Returns `true` if the column is the first column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the first in a sub-section of the table.
33+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-ordering#getisfirstcolumn)
34+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-ordering)
35+
*/
36+
getIsFirstColumn: (position?: ColumnPinningPosition | 'center') => boolean
37+
/**
38+
* Returns `true` if the column is the last column in the order of the visible columns. Optionally pass a `position` parameter to check if the column is the last in a sub-section of the table.
39+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-ordering#getislastcolumn)
40+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-ordering)
41+
*/
42+
getIsLastColumn: (position?: ColumnPinningPosition | 'center') => boolean
43+
}
44+
2345
export interface ColumnOrderDefaultOptions {
2446
onColumnOrderChange: OnChangeFn<ColumnOrderState>
2547
}
@@ -60,6 +82,25 @@ export const Ordering: TableFeature = {
6082
}
6183
},
6284

85+
createColumn: <TData extends RowData>(
86+
column: Column<TData, unknown>,
87+
table: Table<TData>
88+
): void => {
89+
column.getIndex = memo(
90+
position => [_getVisibleLeafColumns(table, position)],
91+
columns => columns.findIndex(d => d.id === column.id),
92+
getMemoOptions(table.options, 'debugColumns', 'getIndex')
93+
)
94+
column.getIsFirstColumn = position => {
95+
const columns = _getVisibleLeafColumns(table, position)
96+
return columns[0]?.id === column.id
97+
}
98+
column.getIsLastColumn = position => {
99+
const columns = _getVisibleLeafColumns(table, position)
100+
return columns[columns.length - 1]?.id === column.id
101+
}
102+
},
103+
63104
createTable: <TData extends RowData>(table: Table<TData>): void => {
64105
table.setColumnOrder = updater =>
65106
table.options.onColumnOrderChange?.(updater)
@@ -74,43 +115,41 @@ export const Ordering: TableFeature = {
74115
table.getState().grouping,
75116
table.options.groupedColumnMode,
76117
],
77-
(columnOrder, grouping, groupedColumnMode) => columns => {
78-
// Sort grouped columns to the start of the column list
79-
// before the headers are built
80-
let orderedColumns: Column<TData, unknown>[] = []
81-
82-
// If there is no order, return the normal columns
83-
if (!columnOrder?.length) {
84-
orderedColumns = columns
85-
} else {
86-
const columnOrderCopy = [...columnOrder]
87-
88-
// If there is an order, make a copy of the columns
89-
const columnsCopy = [...columns]
90-
91-
// And make a new ordered array of the columns
92-
93-
// Loop over the columns and place them in order into the new array
94-
while (columnsCopy.length && columnOrderCopy.length) {
95-
const targetColumnId = columnOrderCopy.shift()
96-
const foundIndex = columnsCopy.findIndex(
97-
d => d.id === targetColumnId
98-
)
99-
if (foundIndex > -1) {
100-
orderedColumns.push(columnsCopy.splice(foundIndex, 1)[0]!)
118+
(columnOrder, grouping, groupedColumnMode) =>
119+
(columns: Column<TData, unknown>[]) => {
120+
// Sort grouped columns to the start of the column list
121+
// before the headers are built
122+
let orderedColumns: Column<TData, unknown>[] = []
123+
124+
// If there is no order, return the normal columns
125+
if (!columnOrder?.length) {
126+
orderedColumns = columns
127+
} else {
128+
const columnOrderCopy = [...columnOrder]
129+
130+
// If there is an order, make a copy of the columns
131+
const columnsCopy = [...columns]
132+
133+
// And make a new ordered array of the columns
134+
135+
// Loop over the columns and place them in order into the new array
136+
while (columnsCopy.length && columnOrderCopy.length) {
137+
const targetColumnId = columnOrderCopy.shift()
138+
const foundIndex = columnsCopy.findIndex(
139+
d => d.id === targetColumnId
140+
)
141+
if (foundIndex > -1) {
142+
orderedColumns.push(columnsCopy.splice(foundIndex, 1)[0]!)
143+
}
101144
}
102-
}
103145

104-
// If there are any columns left, add them to the end
105-
orderedColumns = [...orderedColumns, ...columnsCopy]
106-
}
146+
// If there are any columns left, add them to the end
147+
orderedColumns = [...orderedColumns, ...columnsCopy]
148+
}
107149

108-
return orderColumns(orderedColumns, grouping, groupedColumnMode)
109-
},
110-
{
111-
key: process.env.NODE_ENV === 'development' && 'getOrderColumnsFn',
112-
// debug: () => table.options.debugAll ?? table.options.debugTable,
113-
}
150+
return orderColumns(orderedColumns, grouping, groupedColumnMode)
151+
},
152+
getMemoOptions(table.options, 'debugTable', '_getOrderColumnsFn')
114153
)
115154
},
116155
}

‎packages/table-core/src/features/Pagination.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { TableFeature } from '../core/table'
22
import { OnChangeFn, Table, RowModel, Updater, RowData } from '../types'
3-
import { functionalUpdate, makeStateUpdater, memo } from '../utils'
3+
import {
4+
functionalUpdate,
5+
getMemoOptions,
6+
makeStateUpdater,
7+
memo,
8+
} from '../utils'
49

510
export interface PaginationState {
611
pageIndex: number
@@ -290,10 +295,7 @@ export const Pagination: TableFeature = {
290295
}
291296
return pageOptions
292297
},
293-
{
294-
key: process.env.NODE_ENV === 'development' && 'getPageOptions',
295-
debug: () => table.options.debugAll ?? table.options.debugTable,
296-
}
298+
getMemoOptions(table.options, 'debugTable', 'getPageOptions')
297299
)
298300

299301
table.getCanPreviousPage = () => table.getState().pagination.pageIndex > 0

‎packages/table-core/src/features/Pinning.ts

+34-60
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
Cell,
99
RowData,
1010
} from '../types'
11-
import { makeStateUpdater, memo } from '../utils'
11+
import { getMemoOptions, makeStateUpdater, memo } from '../utils'
1212

1313
export type ColumnPinningPosition = false | 'left' | 'right'
1414
export type RowPinningPosition = false | 'top' | 'bottom'
@@ -426,11 +426,7 @@ export const Pinning: TableFeature = {
426426

427427
return allCells.filter(d => !leftAndRight.includes(d.column.id))
428428
},
429-
{
430-
key:
431-
process.env.NODE_ENV === 'development' && 'row.getCenterVisibleCells',
432-
debug: () => table.options.debugAll ?? table.options.debugRows,
433-
}
429+
getMemoOptions(table.options, 'debugRows', 'getCenterVisibleCells')
434430
)
435431
row.getLeftVisibleCells = memo(
436432
() => [row._getAllVisibleCells(), table.getState().columnPinning.left, ,],
@@ -442,11 +438,7 @@ export const Pinning: TableFeature = {
442438

443439
return cells
444440
},
445-
{
446-
key:
447-
process.env.NODE_ENV === 'development' && 'row.getLeftVisibleCells',
448-
debug: () => table.options.debugAll ?? table.options.debugRows,
449-
}
441+
getMemoOptions(table.options, 'debugRows', 'getLeftVisibleCells')
450442
)
451443
row.getRightVisibleCells = memo(
452444
() => [row._getAllVisibleCells(), table.getState().columnPinning.right],
@@ -458,11 +450,7 @@ export const Pinning: TableFeature = {
458450

459451
return cells
460452
},
461-
{
462-
key:
463-
process.env.NODE_ENV === 'development' && 'row.getRightVisibleCells',
464-
debug: () => table.options.debugAll ?? table.options.debugRows,
465-
}
453+
getMemoOptions(table.options, 'debugRows', 'getRightVisibleCells')
466454
)
467455
},
468456

@@ -493,10 +481,7 @@ export const Pinning: TableFeature = {
493481
.map(columnId => allColumns.find(column => column.id === columnId)!)
494482
.filter(Boolean)
495483
},
496-
{
497-
key: process.env.NODE_ENV === 'development' && 'getLeftLeafColumns',
498-
debug: () => table.options.debugAll ?? table.options.debugColumns,
499-
}
484+
getMemoOptions(table.options, 'debugColumns', 'getLeftLeafColumns')
500485
)
501486

502487
table.getRightLeafColumns = memo(
@@ -506,10 +491,7 @@ export const Pinning: TableFeature = {
506491
.map(columnId => allColumns.find(column => column.id === columnId)!)
507492
.filter(Boolean)
508493
},
509-
{
510-
key: process.env.NODE_ENV === 'development' && 'getRightLeafColumns',
511-
debug: () => table.options.debugAll ?? table.options.debugColumns,
512-
}
494+
getMemoOptions(table.options, 'debugColumns', 'getRightLeafColumns')
513495
)
514496

515497
table.getCenterLeafColumns = memo(
@@ -523,10 +505,7 @@ export const Pinning: TableFeature = {
523505

524506
return allColumns.filter(d => !leftAndRight.includes(d.id))
525507
},
526-
{
527-
key: process.env.NODE_ENV === 'development' && 'getCenterLeafColumns',
528-
debug: () => table.options.debugAll ?? table.options.debugColumns,
529-
}
508+
getMemoOptions(table.options, 'debugColumns', 'getCenterLeafColumns')
530509
)
531510

532511
table.setRowPinning = updater => table.options.onRowPinningChange?.(updater)
@@ -547,34 +526,32 @@ export const Pinning: TableFeature = {
547526
return Boolean(pinningState[position]?.length)
548527
}
549528

550-
table._getPinnedRows = (position: 'top' | 'bottom') =>
551-
memo(
552-
() => [table.getRowModel().rows, table.getState().rowPinning[position]],
553-
(visibleRows, pinnedRowIds) => {
554-
const rows =
555-
table.options.keepPinnedRows ?? true
556-
? //get all rows that are pinned even if they would not be otherwise visible
557-
//account for expanded parent rows, but not pagination or filtering
558-
(pinnedRowIds ?? []).map(rowId => {
559-
const row = table.getRow(rowId, true)
560-
return row.getIsAllParentsExpanded() ? row : null
561-
})
562-
: //else get only visible rows that are pinned
563-
(pinnedRowIds ?? []).map(
564-
rowId => visibleRows.find(row => row.id === rowId)!
565-
)
566-
567-
return rows
568-
.filter(Boolean)
569-
.map(d => ({ ...d, position })) as Row<TData>[]
570-
},
571-
{
572-
key:
573-
process.env.NODE_ENV === 'development' &&
574-
`row.get${position === 'top' ? 'Top' : 'Bottom'}Rows`,
575-
debug: () => table.options.debugAll ?? table.options.debugRows,
576-
}
577-
)()
529+
table._getPinnedRows = memo(
530+
position => [
531+
table.getRowModel().rows,
532+
table.getState().rowPinning[position!],
533+
position,
534+
],
535+
(visibleRows, pinnedRowIds, position) => {
536+
const rows =
537+
table.options.keepPinnedRows ?? true
538+
? //get all rows that are pinned even if they would not be otherwise visible
539+
//account for expanded parent rows, but not pagination or filtering
540+
(pinnedRowIds ?? []).map(rowId => {
541+
const row = table.getRow(rowId, true)
542+
return row.getIsAllParentsExpanded() ? row : null
543+
})
544+
: //else get only visible rows that are pinned
545+
(pinnedRowIds ?? []).map(
546+
rowId => visibleRows.find(row => row.id === rowId)!
547+
)
548+
549+
return rows
550+
.filter(Boolean)
551+
.map(d => ({ ...d, position })) as Row<TData>[]
552+
},
553+
getMemoOptions(table.options, 'debugRows', '_getPinnedRows')
554+
)
578555

579556
table.getTopRows = () => table._getPinnedRows('top')
580557

@@ -590,10 +567,7 @@ export const Pinning: TableFeature = {
590567
const topAndBottom = new Set([...(top ?? []), ...(bottom ?? [])])
591568
return allRows.filter(d => !topAndBottom.has(d.id))
592569
},
593-
{
594-
key: process.env.NODE_ENV === 'development' && 'row.getCenterRows',
595-
debug: () => table.options.debugAll ?? table.options.debugRows,
596-
}
570+
getMemoOptions(table.options, 'debugRows', 'getCenterRows')
597571
)
598572
},
599573
}

‎packages/table-core/src/features/RowSelection.ts

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TableFeature } from '../core/table'
22
import { OnChangeFn, Table, Row, RowModel, Updater, RowData } from '../types'
3-
import { makeStateUpdater, memo } from '../utils'
3+
import { getMemoOptions, makeStateUpdater, memo } from '../utils'
44

55
export type RowSelectionState = Record<string, boolean>
66

@@ -333,10 +333,7 @@ export const RowSelection: TableFeature = {
333333

334334
return selectRowsFn(table, rowModel)
335335
},
336-
{
337-
key: process.env.NODE_ENV === 'development' && 'getSelectedRowModel',
338-
debug: () => table.options.debugAll ?? table.options.debugTable,
339-
}
336+
getMemoOptions(table.options, 'debugTable', 'getSelectedRowModel')
340337
)
341338

342339
table.getFilteredSelectedRowModel = memo(
@@ -352,12 +349,7 @@ export const RowSelection: TableFeature = {
352349

353350
return selectRowsFn(table, rowModel)
354351
},
355-
{
356-
key:
357-
process.env.NODE_ENV === 'production' &&
358-
'getFilteredSelectedRowModel',
359-
debug: () => table.options.debugAll ?? table.options.debugTable,
360-
}
352+
getMemoOptions(table.options, 'debugTable', 'getFilteredSelectedRowModel')
361353
)
362354

363355
table.getGroupedSelectedRowModel = memo(
@@ -373,11 +365,7 @@ export const RowSelection: TableFeature = {
373365

374366
return selectRowsFn(table, rowModel)
375367
},
376-
{
377-
key:
378-
process.env.NODE_ENV === 'production' && 'getGroupedSelectedRowModel',
379-
debug: () => table.options.debugAll ?? table.options.debugTable,
380-
}
368+
getMemoOptions(table.options, 'debugTable', 'getGroupedSelectedRowModel')
381369
)
382370

383371
///

‎packages/table-core/src/features/Visibility.ts

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ColumnPinningPosition } from '..'
12
import { TableFeature } from '../core/table'
23
import {
34
Cell,
@@ -8,7 +9,7 @@ import {
89
Row,
910
RowData,
1011
} from '../types'
11-
import { makeStateUpdater, memo } from '../utils'
12+
import { getMemoOptions, makeStateUpdater, memo } from '../utils'
1213

1314
export type VisibilityState = Record<string, boolean>
1415

@@ -199,10 +200,7 @@ export const Visibility: TableFeature = {
199200
cells => {
200201
return cells.filter(cell => cell.column.getIsVisible())
201202
},
202-
{
203-
key: process.env.NODE_ENV === 'production' && 'row._getAllVisibleCells',
204-
debug: () => table.options.debugAll ?? table.options.debugRows,
205-
}
203+
getMemoOptions(table.options, 'debugRows', '_getAllVisibleCells')
206204
)
207205
row.getVisibleCells = memo(
208206
() => [
@@ -211,10 +209,7 @@ export const Visibility: TableFeature = {
211209
row.getRightVisibleCells(),
212210
],
213211
(left, center, right) => [...left, ...center, ...right],
214-
{
215-
key: process.env.NODE_ENV === 'development' && 'row.getVisibleCells',
216-
debug: () => table.options.debugAll ?? table.options.debugRows,
217-
}
212+
getMemoOptions(table.options, 'debugRows', 'getVisibleCells')
218213
)
219214
},
220215

@@ -234,10 +229,7 @@ export const Visibility: TableFeature = {
234229
columns => {
235230
return columns.filter(d => d.getIsVisible?.())
236231
},
237-
{
238-
key,
239-
debug: () => table.options.debugAll ?? table.options.debugColumns,
240-
}
232+
getMemoOptions(table.options, 'debugColumns', key)
241233
)
242234
}
243235

@@ -300,3 +292,16 @@ export const Visibility: TableFeature = {
300292
}
301293
},
302294
}
295+
296+
export function _getVisibleLeafColumns<TData extends RowData>(
297+
table: Table<TData>,
298+
position?: ColumnPinningPosition | 'center'
299+
) {
300+
return !position
301+
? table.getVisibleLeafColumns()
302+
: position === 'center'
303+
? table.getCenterVisibleLeafColumns()
304+
: position === 'left'
305+
? table.getLeftVisibleLeafColumns()
306+
: table.getRightVisibleLeafColumns()
307+
}

‎packages/table-core/src/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
VisibilityRow,
99
} from './features/Visibility'
1010
import {
11+
ColumnOrderColumn,
1112
ColumnOrderInstance,
1213
ColumnOrderOptions,
1314
ColumnOrderTableState,
@@ -304,7 +305,8 @@ export interface Column<TData extends RowData, TValue = unknown>
304305
FiltersColumn<TData>,
305306
SortingColumn<TData>,
306307
GroupingColumn<TData>,
307-
ColumnSizingColumn {}
308+
ColumnSizingColumn,
309+
ColumnOrderColumn {}
308310

309311
export interface Cell<TData extends RowData, TValue>
310312
extends CoreCell<TData, TValue>,

‎packages/table-core/src/utils.ts

+25-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TableState, Updater } from './types'
1+
import { TableOptionsResolved, TableState, Updater } from './types'
22

33
export type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
44
export type RequiredKeys<T, K extends keyof T> = Omit<T, K> &
@@ -134,23 +134,23 @@ export function flattenBy<TNode>(
134134
return flat
135135
}
136136

137-
export function memo<TDeps extends readonly any[], TResult>(
138-
getDeps: () => [...TDeps],
137+
export function memo<TDeps extends readonly any[], TDepArgs, TResult>(
138+
getDeps: (depArgs?: TDepArgs) => [...TDeps],
139139
fn: (...args: NoInfer<[...TDeps]>) => TResult,
140140
opts: {
141141
key: any
142142
debug?: () => any
143143
onChange?: (result: TResult) => void
144144
}
145-
): () => TResult {
145+
): (depArgs?: TDepArgs) => TResult {
146146
let deps: any[] = []
147147
let result: TResult | undefined
148148

149-
return () => {
149+
return depArgs => {
150150
let depTime: number
151151
if (opts.key && opts.debug) depTime = Date.now()
152152

153-
const newDeps = getDeps()
153+
const newDeps = getDeps(depArgs)
154154

155155
const depsChanged =
156156
newDeps.length !== deps.length ||
@@ -199,3 +199,22 @@ export function memo<TDeps extends readonly any[], TResult>(
199199
return result!
200200
}
201201
}
202+
203+
export function getMemoOptions(
204+
tableOptions: Partial<TableOptionsResolved<any>>,
205+
debugLevel:
206+
| 'debugAll'
207+
| 'debugCells'
208+
| 'debugTable'
209+
| 'debugColumns'
210+
| 'debugRows'
211+
| 'debugHeaders',
212+
key: string,
213+
onChange?: (result: any) => void
214+
) {
215+
return {
216+
debug: () => tableOptions?.debugAll ?? tableOptions[debugLevel],
217+
key: process.env.NODE_ENV === 'development' && key,
218+
onChange,
219+
}
220+
}

‎packages/table-core/src/utils/getCoreRowModel.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createRow } from '../core/row'
22
import { Table, Row, RowModel, RowData } from '../types'
3-
import { memo } from '../utils'
3+
import { getMemoOptions, memo } from '../utils'
44

55
export function getCoreRowModel<TData extends RowData>(): (
66
table: Table<TData>
@@ -75,12 +75,8 @@ export function getCoreRowModel<TData extends RowData>(): (
7575

7676
return rowModel
7777
},
78-
{
79-
key: process.env.NODE_ENV === 'development' && 'getRowModel',
80-
debug: () => table.options.debugAll ?? table.options.debugTable,
81-
onChange: () => {
82-
table._autoResetPageIndex()
83-
},
84-
}
78+
getMemoOptions(table.options, 'debugTable', 'getRowModel', () =>
79+
table._autoResetPageIndex()
80+
)
8581
)
8682
}

‎packages/table-core/src/utils/getExpandedRowModel.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Table, Row, RowModel, RowData } from '../types'
2-
import { memo } from '../utils'
2+
import { getMemoOptions, memo } from '../utils'
33

44
export function getExpandedRowModel<TData extends RowData>(): (
55
table: Table<TData>
@@ -26,10 +26,7 @@ export function getExpandedRowModel<TData extends RowData>(): (
2626

2727
return expandRows(rowModel)
2828
},
29-
{
30-
key: process.env.NODE_ENV === 'development' && 'getExpandedRowModel',
31-
debug: () => table.options.debugAll ?? table.options.debugTable,
32-
}
29+
getMemoOptions(table.options, 'debugTable', 'getExpandedRowModel')
3330
)
3431
}
3532

‎packages/table-core/src/utils/getFacetedMinMaxValues.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Table, RowData } from '../types'
2-
import { memo } from '../utils'
2+
import { getMemoOptions, memo } from '../utils'
33

44
export function getFacetedMinMaxValues<TData extends RowData>(): (
55
table: Table<TData>,
@@ -37,12 +37,6 @@ export function getFacetedMinMaxValues<TData extends RowData>(): (
3737

3838
return facetedMinMaxValues
3939
},
40-
{
41-
key:
42-
process.env.NODE_ENV === 'development' &&
43-
'getFacetedMinMaxValues_' + columnId,
44-
debug: () => table.options.debugAll ?? table.options.debugTable,
45-
onChange: () => {},
46-
}
40+
getMemoOptions(table.options, 'debugTable', 'getFacetedMinMaxValues')
4741
)
4842
}

‎packages/table-core/src/utils/getFacetedRowModel.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Table, RowModel, Row, RowData } from '../types'
2-
import { memo } from '../utils'
2+
import { getMemoOptions, memo } from '../utils'
33
import { filterRows } from './filterRowsUtils'
44

55
export function getFacetedRowModel<TData extends RowData>(): (
@@ -39,12 +39,6 @@ export function getFacetedRowModel<TData extends RowData>(): (
3939

4040
return filterRows(preRowModel.rows, filterRowsImpl, table)
4141
},
42-
{
43-
key:
44-
process.env.NODE_ENV === 'development' &&
45-
'getFacetedRowModel_' + columnId,
46-
debug: () => table.options.debugAll ?? table.options.debugTable,
47-
onChange: () => {},
48-
}
42+
getMemoOptions(table.options, 'debugTable', 'getFacetedRowModel')
4943
)
5044
}

‎packages/table-core/src/utils/getFacetedUniqueValues.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Table, RowData } from '../types'
2-
import { memo } from '../utils'
2+
import { getMemoOptions, memo } from '../utils'
33

44
export function getFacetedUniqueValues<TData extends RowData>(): (
55
table: Table<TData>,
@@ -33,12 +33,10 @@ export function getFacetedUniqueValues<TData extends RowData>(): (
3333

3434
return facetedUniqueValues
3535
},
36-
{
37-
key:
38-
process.env.NODE_ENV === 'development' &&
39-
'getFacetedUniqueValues_' + columnId,
40-
debug: () => table.options.debugAll ?? table.options.debugTable,
41-
onChange: () => {},
42-
}
36+
getMemoOptions(
37+
table.options,
38+
'debugTable',
39+
`getFacetedUniqueValues_${columnId}`
40+
)
4341
)
4442
}

‎packages/table-core/src/utils/getFilteredRowModel.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ResolvedColumnFilter } from '../features/Filters'
22
import { Table, RowModel, Row, RowData } from '../types'
3-
import { memo } from '../utils'
3+
import { getMemoOptions, memo } from '../utils'
44
import { filterRows } from './filterRowsUtils'
55

66
export function getFilteredRowModel<TData extends RowData>(): (
@@ -144,12 +144,8 @@ export function getFilteredRowModel<TData extends RowData>(): (
144144
// Filter final rows using all of the active filters
145145
return filterRows(rowModel.rows, filterRowsImpl, table)
146146
},
147-
{
148-
key: process.env.NODE_ENV === 'development' && 'getFilteredRowModel',
149-
debug: () => table.options.debugAll ?? table.options.debugTable,
150-
onChange: () => {
151-
table._autoResetPageIndex()
152-
},
153-
}
147+
getMemoOptions(table.options, 'debugTable', 'getFilteredRowModel', () =>
148+
table._autoResetPageIndex()
149+
)
154150
)
155151
}

‎packages/table-core/src/utils/getGroupedRowModel.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createRow } from '../core/row'
22
import { Table, Row, RowModel, RowData } from '../types'
3-
import { flattenBy, memo } from '../utils'
3+
import { flattenBy, getMemoOptions, memo } from '../utils'
44

55
export function getGroupedRowModel<TData extends RowData>(): (
66
table: Table<TData>
@@ -156,16 +156,12 @@ export function getGroupedRowModel<TData extends RowData>(): (
156156
rowsById: groupedRowsById,
157157
}
158158
},
159-
{
160-
key: process.env.NODE_ENV === 'development' && 'getGroupedRowModel',
161-
debug: () => table.options.debugAll ?? table.options.debugTable,
162-
onChange: () => {
163-
table._queue(() => {
164-
table._autoResetExpanded()
165-
table._autoResetPageIndex()
166-
})
167-
},
168-
}
159+
getMemoOptions(table.options, 'debugTable', 'getGroupedRowModel', () => {
160+
table._queue(() => {
161+
table._autoResetExpanded()
162+
table._autoResetPageIndex()
163+
})
164+
})
169165
)
170166
}
171167

‎packages/table-core/src/utils/getPaginationRowModel.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Table, RowModel, Row, RowData } from '../types'
2-
import { memo } from '../utils'
2+
import { getMemoOptions, memo } from '../utils'
33
import { expandRows } from './getExpandedRowModel'
44

55
export function getPaginationRowModel<TData extends RowData>(opts?: {
@@ -55,9 +55,6 @@ export function getPaginationRowModel<TData extends RowData>(opts?: {
5555

5656
return paginatedRowModel
5757
},
58-
{
59-
key: process.env.NODE_ENV === 'development' && 'getPaginationRowModel',
60-
debug: () => table.options.debugAll ?? table.options.debugTable,
61-
}
58+
getMemoOptions(table.options, 'debugTable', 'getPaginationRowModel')
6259
)
6360
}

‎packages/table-core/src/utils/getSortedRowModel.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Table, Row, RowModel, RowData } from '../types'
22
import { SortingFn } from '../features/Sorting'
3-
import { memo } from '../utils'
3+
import { getMemoOptions, memo } from '../utils'
44

55
export function getSortedRowModel<TData extends RowData>(): (
66
table: Table<TData>
@@ -111,12 +111,8 @@ export function getSortedRowModel<TData extends RowData>(): (
111111
rowsById: rowModel.rowsById,
112112
}
113113
},
114-
{
115-
key: process.env.NODE_ENV === 'development' && 'getSortedRowModel',
116-
debug: () => table.options.debugAll ?? table.options.debugTable,
117-
onChange: () => {
118-
table._autoResetPageIndex()
119-
},
120-
}
114+
getMemoOptions(table.options, 'debugTable', 'getSortedRowModel', () =>
115+
table._autoResetPageIndex()
116+
)
121117
)
122118
}

‎pnpm-lock.yaml

+34-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.