Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/header-get-is-pinned.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/table-core': minor
---

Add `header.getIsPinned()`, which reads the pinned region of a header's own leaf columns. A group column split across pinned regions renders one header per region, and `header.column.getIsPinned()` reported the same region for all of them (#4906).
10 changes: 4 additions & 6 deletions docs/framework/alpine/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down Expand Up @@ -214,19 +215,16 @@ Because Alpine does not initialize directives inside content set with `x-html`,
<template x-if="!header.isPlaceholder && header.column.getCanPin()">
<div class="pin-actions">
<button
x-show="header.column.getIsPinned() !== 'start'"
x-show="header.getIsPinned() !== 'start'"
@click="header.column.pin('start')"
>
&lt;=
</button>
<button
x-show="header.column.getIsPinned()"
@click="header.column.pin(false)"
>
<button x-show="header.getIsPinned()" @click="header.column.pin(false)">
X
</button>
<button
x-show="header.column.getIsPinned() !== 'end'"
x-show="header.getIsPinned() !== 'end'"
@click="header.column.pin('end')"
>
=&gt;
Expand Down
1 change: 1 addition & 0 deletions docs/framework/angular/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
27 changes: 17 additions & 10 deletions docs/framework/ember/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down Expand Up @@ -202,20 +203,26 @@ Use `table.getIsSomeColumnsPinned()` to check if any columns are pinned, or pass
Because Ember templates extract method references without binding them, wrap these Column and Table method calls in small module-scope helper functions (or getters), then call the helpers from your markup:

```ts
import { type Column, type ColumnPinningPosition } from '@tanstack/ember-table'
import {
type Column,
type ColumnPinningPosition,
type Header,
} from '@tanstack/ember-table'

const getCanPin = (column: Column<typeof features, Person>): boolean =>
column.getCanPin()

// Read pin state from the header, so a group split across pinned regions
// reports the region this header is rendered in
const getIsPinned = (
column: Column<typeof features, Person>,
): ColumnPinningPosition => column.getIsPinned()
header: Header<typeof features, Person>,
): ColumnPinningPosition => header.getIsPinned()

const isPinnedStart = (column: Column<typeof features, Person>): boolean =>
column.getIsPinned() === 'start'
const isPinnedStart = (header: Header<typeof features, Person>): boolean =>
header.getIsPinned() === 'start'

const isPinnedEnd = (column: Column<typeof features, Person>): boolean =>
column.getIsPinned() === 'end'
const isPinnedEnd = (header: Header<typeof features, Person>): boolean =>
header.getIsPinned() === 'end'

const pin = (
column: Column<typeof features, Person>,
Expand All @@ -228,19 +235,19 @@ const pin = (
```hbs
{{#if (getCanPin header.column)}}
<div class='pin-actions'>
{{#unless (isPinnedStart header.column)}}
{{#unless (isPinnedStart header)}}
<button
class='pin-button'
{{on 'click' (pin header.column 'start')}}
>◀</button>
{{/unless}}
{{#if (getIsPinned header.column)}}
{{#if (getIsPinned header)}}
<button
class='pin-button'
{{on 'click' (pin header.column false)}}
>✕</button>
{{/if}}
{{#unless (isPinnedEnd header.column)}}
{{#unless (isPinnedEnd header)}}
<button
class='pin-button'
{{on 'click' (pin header.column 'end')}}
Expand Down
1 change: 1 addition & 0 deletions docs/framework/lit/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
1 change: 1 addition & 0 deletions docs/framework/octane/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
1 change: 1 addition & 0 deletions docs/framework/preact/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
1 change: 1 addition & 0 deletions docs/framework/react/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
1 change: 1 addition & 0 deletions docs/framework/solid/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
1 change: 1 addition & 0 deletions docs/framework/svelte/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
1 change: 1 addition & 0 deletions docs/framework/vue/guide/column-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ There are a handful of useful Column API methods to help you implement column pi
- `column.getCanPin`: Use to determine if a column can be pinned.
- `column.pin`: Use to pin a column to the start or end. Or use to unpin a column.
- `column.getIsPinned`: Use to determine where a column is pinned.
- `header.getIsPinned`: Use to determine where a header is pinned. A group header split across pinned regions reports the region it is rendered in, which `header.column.getIsPinned` cannot.
- `column.getPinnedIndex`: Use to read the column's index within its pinned column group.
- `column.getStart`: Use to provide the correct `start` CSS value for a pinned column.
- `column.getAfter`: Use to provide the correct `end` CSS value for a pinned column.
Expand Down
18 changes: 9 additions & 9 deletions examples/react/column-pinning-split/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ function App() {
</div>
{!header.isPlaceholder && header.column.getCanPin() && (
<div className="pin-actions">
{header.column.getIsPinned() !== 'start' ? (
{header.getIsPinned() !== 'start' ? (
<button
className="pin-button"
onClick={() => {
Expand All @@ -174,7 +174,7 @@ function App() {
{'<='}
</button>
) : null}
{header.column.getIsPinned() ? (
{header.getIsPinned() ? (
<button
className="pin-button"
onClick={() => {
Expand All @@ -184,7 +184,7 @@ function App() {
X
</button>
) : null}
{header.column.getIsPinned() !== 'end' ? (
{header.getIsPinned() !== 'end' ? (
<button
className="pin-button"
onClick={() => {
Expand Down Expand Up @@ -233,7 +233,7 @@ function App() {
</div>
{!header.isPlaceholder && header.column.getCanPin() && (
<div className="pin-actions">
{header.column.getIsPinned() !== 'start' ? (
{header.getIsPinned() !== 'start' ? (
<button
className="pin-button"
onClick={() => {
Expand All @@ -243,7 +243,7 @@ function App() {
{'<='}
</button>
) : null}
{header.column.getIsPinned() ? (
{header.getIsPinned() ? (
<button
className="pin-button"
onClick={() => {
Expand All @@ -253,7 +253,7 @@ function App() {
X
</button>
) : null}
{header.column.getIsPinned() !== 'end' ? (
{header.getIsPinned() !== 'end' ? (
<button
className="pin-button"
onClick={() => {
Expand Down Expand Up @@ -302,7 +302,7 @@ function App() {
</div>
{!header.isPlaceholder && header.column.getCanPin() && (
<div className="pin-actions">
{header.column.getIsPinned() !== 'start' ? (
{header.getIsPinned() !== 'start' ? (
<button
className="pin-button"
onClick={() => {
Expand All @@ -312,7 +312,7 @@ function App() {
{'<='}
</button>
) : null}
{header.column.getIsPinned() ? (
{header.getIsPinned() ? (
<button
className="pin-button"
onClick={() => {
Expand All @@ -322,7 +322,7 @@ function App() {
X
</button>
) : null}
{header.column.getIsPinned() !== 'end' ? (
{header.getIsPinned() !== 'end' ? (
<button
className="pin-button"
onClick={() => {
Expand Down
30 changes: 30 additions & 0 deletions examples/react/column-pinning-split/tests/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,36 @@ test('returns a column to the centre table when unpinned', async ({ page }) => {
expect(errors).toEqual([])
})

test('offers pin buttons for the region a split group header is in', async ({
page,
}) => {
const errors = await openExample(page)

await pinButton(page, CENTER_TABLE, 'firstName', '<=').click()
await expect.poll(() => leafCount(page, START_TABLE)).toBe(1)
await pinButton(page, CENTER_TABLE, 'Last Name', '=>').click()
await expect.poll(() => leafCount(page, END_TABLE)).toBe(1)

// The "Name" group is split, so the start and end tables each render a
// "Name" header for the same group column. Each must act on its own region.
const groupPinButton = (tableIndex: number, label: '<=' | 'X' | '=>') =>
tableAt(page, tableIndex)
.locator('thead tr')
.first()
.locator('th')
.filter({ hasText: 'Name' })
.getByRole('button', { name: label, exact: true })

await expect(groupPinButton(START_TABLE, '<=')).toHaveCount(0)
await expect(groupPinButton(START_TABLE, 'X')).toHaveCount(1)
await expect(groupPinButton(START_TABLE, '=>')).toHaveCount(1)
await expect(groupPinButton(END_TABLE, '<=')).toHaveCount(1)
await expect(groupPinButton(END_TABLE, 'X')).toHaveCount(1)
await expect(groupPinButton(END_TABLE, '=>')).toHaveCount(0)

expect(errors).toEqual([])
})

test('keeps every table showing the same rows', async ({ page }) => {
const errors = await openExample(page)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
column_getPinnedIndex,
column_pin,
getDefaultColumnPinningState,
header_getIsPinned,
row_getCenterVisibleCells,
row_getEndVisibleCells,
row_getStartVisibleCells,
Expand Down Expand Up @@ -83,6 +84,14 @@ export const columnPinningFeature: TableFeature = {
})
},

assignHeaderPrototype: (prototype, table) => {
assignPrototypeAPIs('columnPinningFeature', prototype, table, {
header_getIsPinned: {
fn: (header) => header_getIsPinned(header),
},
})
},

assignRowPrototype: (prototype, table) => {
assignPrototypeAPIs('columnPinningFeature', prototype, table, {
row_getCenterVisibleCells: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export interface Column_ColumnPinning {
pin: (position: ColumnPinningPosition) => void
}

export interface Header_ColumnPinning {
/**
* Reads the logical pinned position shared by this header's own leaf
* columns: `'start'`, `'end'`, or `false` when unpinned or mixed. Unlike
* `header.column.getIsPinned()`, a group header split across regions
* reports the region it is rendered in.
*/
getIsPinned: () => ColumnPinningPosition
}

export interface Row_ColumnPinning<
in out TFeatures extends TableFeatures,
in out TData extends RowData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,48 @@ export function column_getPinnedIndex<
: 0
}

// Header APIs

/**
* Reads the pinned region of this header's own leaf columns.
*
* A group column split across regions renders one header per region, so its
* column reports a single position. Each header reads only the leaf columns
* it renders: returns their shared position, or `false` when they are
* unpinned or mixed.
*
* @example
* ```ts
* const position = header_getIsPinned(header)
* ```
*/
export function header_getIsPinned<
TFeatures extends TableFeatures,
TData extends RowData,
TValue extends CellData = CellData,
>(header: Header<TFeatures, TData, TValue>): ColumnPinningPosition {
const leafHeaders = header.getLeafHeaders()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
let position: ColumnPinningPosition | undefined

for (let i = 0; i < leafHeaders.length; i++) {
const leafHeader = leafHeaders[i]!
// getLeafHeaders() also returns the group headers themselves, whose
// shared column would report every region its leaves are pinned to
if (leafHeader.subHeaders.length) {
continue
}

const leafPosition = column_getIsPinned(leafHeader.column)
if (position === undefined) {
position = leafPosition
} else if (leafPosition !== position) {
return false
}
}

return position ?? false
}

// Row APIs

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/table-core/src/types/Header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { CellData, RowData } from './type-utils'
import type { ExtractFeatureMapTypes, TableFeatures } from './TableFeatures'
import type { Header_Header } from '../core/headers/coreHeadersFeature.types'
import type { Header_ColumnResizing } from '../features/column-resizing/columnResizingFeature.types'
import type { Header_ColumnPinning } from '../features/column-pinning/columnPinningFeature.types'

export interface Header_Core<
in out TFeatures extends TableFeatures,
Expand All @@ -11,6 +12,7 @@ export interface Header_Core<
> extends Header_Header<TFeatures, TData, TValue> {}

export interface Header_FeatureMap {
columnPinningFeature: Header_ColumnPinning
columnSizingFeature: Header_ColumnSizing
columnResizingFeature: Header_ColumnResizing
}
Expand Down
Loading