Rows

Sticky Groups

Scroll deep into a long group and the row that names it is gone. stickyGroups keeps it pinned under the header, so the question "which group am I in" stops needing a scroll back up to answer.

Basic Usage

It is an option of the grid, and it needs virtualization() beside whatever builds the groups. Scroll the grid below: each team's row stays under the header until the next team takes its place. Switch it off and scroll again to see what it was doing.

Team
Role
Name
Country
Salary
Design
Manager
Hoang Kowalski
Poland
$127,691.00
Design
Manager
Bruno Dubois
Nigeria
$86,538.00
Design
Engineer
Tuan Ivanov
Japan
$105,520.00
Design
Manager
Quyen Silva
Japan
$121,817.00
Design
Designer
Chi Silva
Poland
$130,525.00
Design
Analyst
Mai Dubois
Poland
$132,603.00
Design
Designer
Bruno Costa
Nigeria
$106,432.00
Design
Support
Farid Silva
Nigeria
$126,401.00
Design
Analyst
Bruno Ivanov
Germany
$51,954.00
Design
Designer
Hoang Okafor
Germany
$58,263.00
Design
Support
Quyen Costa
Nigeria
$65,220.00
Design
Engineer
Olga Ivanov
Poland
$50,315.00
Design
Engineer
Elif Rossi
Nigeria
$139,492.00
Design
Support
Keiko Novak
Brazil
$89,887.00
Design
Analyst
Ines Andersen
Nigeria
$91,342.00
Design
Engineer
Ada Bergstrom
Germany
$70,431.00
Design
Manager
Ada Haddad
Nigeria
$77,945.00
Design
Support
Farid Ivanov
Brazil
$127,530.00
Design
Engineer
Quyen Novak
Poland
$53,710.00
300 rows
<script lang="ts">
  import { createDataGrid, DataGrid, grouping, virtualization } from '@sv5ui/datagrid';

  const grid = createDataGrid<Person>({
    columns,
    data: people,
    getRowId,
    // An option of the grid, not of grouping(): it works for anything
    // that gives rows a level.
    stickyGroups: true,
    features: [
      // Required. Virtualization is what knows where a row sits against
      // the scroll, so without it nothing can say which rows have gone
      // above the fold, and nothing sticks.
      virtualization<Person>({ rowHeight: 40 }),
      grouping<Person>({ by: ['team'], expandedByDefault: true })
    ]
  });

  // State on the grid, so it can be switched later.
  grid.stickyGroups = false;
</script>

<!-- A virtual grid needs a height to scroll inside. -->
<DataGrid {grid} class="h-96" />

Nested Groups

Each level gets a line, outermost first. Scroll into the designers of one team and both the team and the role are on screen, so a reader knows where they are in the whole tree and not only in the nearest branch.

Team
Role
Name
Country
Salary
Design
Manager
Hoang Kowalski
Poland
$127,691.00
Design
Manager
Bruno Dubois
Nigeria
$86,538.00
Design
Manager
Quyen Silva
Japan
$121,817.00
Design
Manager
Ada Haddad
Nigeria
$77,945.00
Design
Manager
Mai Andersen
Brazil
$126,727.00
Design
Manager
Elif Novak
Germany
$71,695.00
Design
Manager
Mai Nguyen
Vietnam
$124,989.00
Design
Manager
Jonas Dubois
Poland
$66,569.00
Design
Manager
Soren Kowalski
Japan
$74,157.00
Design
Manager
Bruno Muller
Vietnam
$53,481.00
Design
Manager
Farid Yilmaz
Poland
$117,018.00
Design
Manager
Grace Silva
Poland
$100,692.00
Design
Manager
Soren Kowalski
Brazil
$75,594.00
Design
Manager
Rania Bergstrom
Brazil
$81,748.00
Design
Engineer
Tuan Ivanov
Japan
$105,520.00
Design
Engineer
Olga Ivanov
Poland
$50,315.00
Design
Engineer
Elif Rossi
Nigeria
$139,492.00
300 rows
createDataGrid<Person>({
  columns,
  data: people,
  getRowId,
  stickyGroups: true,
  features: [
    virtualization<Person>({ rowHeight: 40 }),
    // Two levels, so two lines stick: the team, then the role inside it.
    grouping<Person>({ by: ['team', 'role'], expandedByDefault: true })
  ]
});

The stack is as deep as the grouping, and it is taken out of the viewport. A grid grouped six deep spends six rows of its height on it. This is something to turn on for shallow groups over long data, not for every grid.

On a Tree

There is no grouping in the grid below, only tree(). What sticks is the parent rows, because the mechanism follows the level a row sits at rather than any one feature. The grid finds the first row still below the top edge and walks back up through rows of ever shallower level, which is exactly the chain of ancestors of the place being read.

Name
Kind
Member 1 of Engineering team 1
Person
Member 2 of Engineering team 1
Person
Member 3 of Engineering team 1
Person
Member 4 of Engineering team 1
Person
Member 5 of Engineering team 1
Person
Member 6 of Engineering team 1
Person
Member 7 of Engineering team 1
Person
Member 8 of Engineering team 1
Person
Member 9 of Engineering team 1
Person
Member 10 of Engineering team 1
Person
Member 11 of Engineering team 1
Person
Member 12 of Engineering team 1
Person
Member 13 of Engineering team 1
Person
Member 14 of Engineering team 1
Person
Member 1 of Engineering team 2
Person
Member 2 of Engineering team 2
Person
Member 3 of Engineering team 2
Person
138 rows
createDataGrid<Unit>({
  columns,
  data: units,
  getRowId: (unit) => unit.id,
  stickyGroups: true,
  features: [
    virtualization<Unit>({ rowHeight: 40 }),
    // No grouping here. What sticks is the parent row, because the
    // mechanism follows a row's level rather than any one feature.
    tree<Unit>({ getParentId: (unit) => unit.parent, defaultExpandedDepth: 99 })
  ]
});

The same is true of a pivot, whose rows are groups. A flat grid has no levels, so there the option does nothing.

With Pinned Rows

A row pinned to the top with rowPinning() is sticky as well. The two do not fight: the pinned row sits under the header and the group line sits under the pinned row. Both offsets are added up from the heights the header really draws with, so a header height of your own does not push either out of place.

Team
Role
Name
Country
Salary
Design
Manager
Hoang Kowalski
Poland
$127,691.00
Design
Manager
Bruno Dubois
Nigeria
$86,538.00
Design
Engineer
Tuan Ivanov
Japan
$105,520.00
Design
Manager
Quyen Silva
Japan
$121,817.00
Design
Designer
Chi Silva
Poland
$130,525.00
Design
Analyst
Mai Dubois
Poland
$132,603.00
Design
Designer
Bruno Costa
Nigeria
$106,432.00
Design
Support
Farid Silva
Nigeria
$126,401.00
Design
Analyst
Bruno Ivanov
Germany
$51,954.00
Design
Designer
Hoang Okafor
Germany
$58,263.00
Design
Support
Quyen Costa
Nigeria
$65,220.00
Design
Engineer
Olga Ivanov
Poland
$50,315.00
Design
Engineer
Elif Rossi
Nigeria
$139,492.00
Design
Support
Keiko Novak
Brazil
$89,887.00
Design
Analyst
Ines Andersen
Nigeria
$91,342.00
Design
Engineer
Ada Bergstrom
Germany
$70,431.00
Design
Manager
Ada Haddad
Nigeria
$77,945.00
Design
Support
Farid Ivanov
Brazil
$127,530.00
Design
Engineer
Quyen Novak
Poland
$53,710.00
Design
Manager
Mai Andersen
Brazil
$126,727.00
300 rows
createDataGrid<Person>({
  columns,
  data: people,
  getRowId,
  stickyGroups: true,
  features: [
    virtualization<Person>({ rowHeight: 40 }),
    rowPinning<Person>({ isRowPinned: (person) => (person.id === 1 ? 'top' : null) }),
    grouping<Person>({ by: ['team'], expandedByDefault: true })
  ]
});

// Both bands are sticky. The pinned row sits under the header, and the
// group line sits under the pinned row.

A Signpost, Not a Second Set of Controls

The pinned lines are copies of rows that exist further up, so they are kept out of the way of everything except the eye. The band is inert and hidden from assistive technology. It adds no tab stop, nothing in it can be clicked or focused, and a screen reader hears each group once, from the row itself. To fold a group, a reader scrolls back to its row.

Only a row that leads a group can stick. A group footer sits at the same level as the rows it closes and is never taken for their ancestor, and a detail panel is a leaf, so scrolling through a long one pins nothing.

Option

OptionDescription
stickyGroupsOn createDataGrid, and readable and settable as grid.stickyGroups. Keeps the group rows a reader is inside pinned under the header, one line per level

Slots

Both go through ui, like every other slot on Theming.

SlotWhat it styles
stickyGroupRowsThe band holding the pinned lines, under the header
stickyGroupRowOne pinned line