Rows

Row Pinning

A pinned row leaves the flow and sticks to the top or the bottom of the viewport. It is taken from the source rows, before filtering and sorting, so it stays put while everything else moves under it.

Basic Usage

Suspended members start pinned to the top. Sort by Salary or type in the quick filter and they stay where they are, because the pin split happens before the row ever reaches those stages.

top: Farid Haddad, Quyen Tanaka, Bruno Yilmaz bottom: none
Status
Farid Haddad
suspended
Growth
$76,443.00
Quyen Tanaka
suspended
Growth
$72,011.00
Bruno Yilmaz
suspended
Core
$139,212.00
Hoang Kowalski
invited
Design
$127,691.00
Bruno Nguyen
invited
Growth
$105,146.00
Bruno Dubois
active
Design
$86,538.00
Jonas Yilmaz
active
Core
$129,812.00
Mai Nguyen
invited
Support
$83,226.00
Bruno Novak
active
Support
$71,507.00
Grace Dubois
active
Support
$94,212.00
Tuan Ivanov
active
Design
$105,520.00
Bruno Andersen
active
Growth
$62,389.00
12 rows
import { createDataGrid, rowPinning } from '@sv5ui/datagrid';

const grid = createDataGrid<Person>({
  data: people,
  columns,
  getRowId,
  features: [
    filtering(),
    sorting(),
    rowPinning({
      // The starting side for a row, or null to leave it in the flow.
      isRowPinned: (person) => (person.status === 'suspended' ? 'top' : null)
    })
  ]
});

// A pinned row leaves the flow: it is taken from the source rows, so
// filtering and sorting never see it and it stays put while the middle
// section moves.

Pinning From Code

An override set by pinRow wins over the predicate, so a row the predicate pinned can be released, and one it left alone can be pinned. The grid below starts with the suspended rows pinned to the top by a predicate.

Farid Haddad top, Quyen Tanaka top
Status
Farid Haddad
suspended
Growth
$76,443.00
Quyen Tanaka
suspended
Growth
$72,011.00
Hoang Kowalski
invited
Design
$127,691.00
Bruno Nguyen
invited
Growth
$105,146.00
Bruno Dubois
active
Design
$86,538.00
Jonas Yilmaz
active
Core
$129,812.00
Mai Nguyen
invited
Support
$83,226.00
Bruno Novak
active
Support
$71,507.00
8 rows
import { getRowPinning } from '@sv5ui/datagrid';

const pinning = getRowPinning(grid);

pinning?.pinRow('3', 'top');
pinning?.pinRow('3', 'bottom');
pinning?.pinRow('3', null);      // back into the flow

pinning?.topNodes;               // RowNode[] pinned to the top
pinning?.bottomNodes;
pinning?.pinnedCount;
pinning?.sideOf(node);           // 'top' | 'bottom' | null
pinning?.getPinnedRows();        // { top: TRow[], bottom: TRow[] }

// Also on the flat api
grid.api.pinRow?.('3', 'top');

grid.events.on('rowPinnedChanged', ({ id, side }) => save(id, side));

The Context Menu

Right-click any row below. The grid has no predicate, so every row offers Pin row top and Pin row bottom, and a pinned row offers to unpin instead. The items come from the feature, not from the menu: DataGrid wires it for you, and Grid.ContextMenu carries them when you compose the chrome by hand.

0 pinned
Status
Hoang Kowalski
invited
Design
$127,691.00
Bruno Nguyen
invited
Growth
$105,146.00
Bruno Dubois
active
Design
$86,538.00
Farid Haddad
suspended
Growth
$76,443.00
Jonas Yilmaz
active
Core
$129,812.00
Quyen Tanaka
suspended
Growth
$72,011.00
Mai Nguyen
invited
Support
$83,226.00
Bruno Novak
active
Support
$71,507.00
8 rows
<!-- The feature contributes its own context-menu items: Pin row top,
     Pin row bottom, and Unpin row on a row that is already pinned.
     DataGrid wires the context menu for you; compose it by hand and it is
     Grid.ContextMenu that carries them. -->
<Grid.Root {grid}>
  <Grid.ContextMenu>
    <Grid.Viewport>
      <Grid.Header />
      <Grid.Body />
    </Grid.Viewport>
  </Grid.ContextMenu>
</Grid.Root>

Full-Width Rows

A row whose node carries meta.fullWidth is drawn as one cell across every column, through the fullWidthRow snippet. That is how a group header or a detail panel is rendered, and such a row is never editable and never selectable.

Name
Status
Team
Salary
Design team
Bruno Nguyen
invited
Growth
$105,146.00
Bruno Dubois
active
Design
$86,538.00
Growth team
Jonas Yilmaz
active
Core
$129,812.00
Quyen Tanaka
suspended
Growth
$72,011.00
4 of 6 rows
<script lang="ts">
  import { PIPELINE_ORDER, type GridFeature } from '@sv5ui/datagrid';

  // meta belongs to the pipeline, so a stage is what sets it. Six lines is
  // a whole feature: this one flags the rows the snippet draws across.
  const groupRowFeature = (): GridFeature<Row> => ({
    id: 'group-rows',
    pipelineStage: {
      order: PIPELINE_ORDER.flatten,
      transform: (nodes) =>
        nodes.map((node) =>
          node.row.fullWidth ? { ...node, meta: { ...node.meta, fullWidth: true } } : node
        )
    }
  });

  const grid = createDataGrid<Row>({
    data: rows,
    columns,
    getRowId,
    features: [groupRowFeature()]
  });
</script>

{#snippet groupHeader({ row })}
  <span class="font-medium">{row.team} team</span>
{/snippet}

<DataGrid {grid} fullWidthRow={groupHeader} />

rowPinning() Options

OptionDefault
isRowPinned-

Row Pinning State

What getRowPinning(grid) exposes. pinRow and getPinnedRows are also on grid.api.

MemberDescription
pinRow(id, side)Pins a row to an edge, or returns it to the flow with null
topNodes / bottomNodesThe pinned rows
pinnedCountHow many rows are pinned in total
sideOf(node)Where one row sits, override first, predicate second
getPinnedRows()The rows themselves rather than the nodes
pinnedOverridesWhat pinRow has set, which is what a snapshot would carry