# useTableGroupedRows

## Usage

Hook that groups a flat data array into collapsible section rows. Each distinct groupBy value becomes a full-width section-header row with a chevron toggle, the group label, and a member count; collapsing hides that group's data rows while keeping the header visible. The disclosure state lives on the chevron button, not the native table row. Mirrors useTableTreeState: the consumer owns the collapsedGroups set and the hook returns {data, plugin, idKey}: pass them to Table as data, plugins, and idKey respectively. Grouping runs on the rows you hand it, so with pagination the order is filter, sort, slice, then group: sort by the group key first and the user's keys second, so a section's rows stay contiguous and each page appends to the bottom of the table instead of splicing rows in above the reader. A page that ends on a row count still cuts mid-section, and the heading then counts what has loaded rather than what exists: "6" quietly becoming "10". Where the full result set is in hand, carry the cut forward to the end of the section it lands in: every rendered section is then whole and its count is a total.

```ts
import { useTableGroupedRows } from '@astryx-vue/core/Table'
```

`data` is a computed, so the table re-renders only when the config changes. Give the config as a getter, and replace the collapsed set with a new one instead of mutating it in place; a mutated set is the same object and the computed never re-runs.

```ts
const { data, plugin, idKey } = useTableGroupedRows(() => ({
  data: employees,
  groupBy: employee => employee.team,
  collapsedGroups: collapsed.value,
  onToggleGroup: toggleGroup,
  getRowKey: employee => employee.id,
}))
```

A section header is an ordinary object in the array. Use the exported `isTableGroupHeader(row)` to tell one from a data row, for example before numbering rows.

## Examples

### useTableGroupedRows — Collapsible Groups

A table grouped into collapsible sections with useTableGroupedRows. Each group gets a full-width header with a chevron, label, and member count; click to collapse or expand.

```vue
<script setup lang="ts">
import type { TableColumn } from '@astryx-vue/core/Table'
import { pixel, proportional, Table, useTableGroupedRows, useTableRowIndex } from '@astryx-vue/core/Table'
import { ref } from 'vue'

interface Employee extends Record<string, unknown> {
  id: string
  name: string
  role: string
}

const employees: Employee[] = [
  { id: '1', name: 'Alice', role: 'Engineer' },
  { id: '2', name: 'Bob', role: 'Designer' },
  { id: '3', name: 'Carol', role: 'Engineer' },
  { id: '4', name: 'Diego', role: 'Designer' },
]

const columns: TableColumn<Employee>[] = [
  { key: 'name', header: 'Name', width: proportional(1) },
  { key: 'role', header: 'Role', width: pixel(140) },
]

const collapsed = ref(new Set<string>())

function toggleGroup(key: string) {
  const next = new Set(collapsed.value)
  if (next.has(key))
    next.delete(key)
  else
    next.add(key)
  collapsed.value = next
}

const { data: groupedRows, plugin: groupedPlugin, idKey } = useTableGroupedRows<Employee>(() => ({
  data: employees,
  groupBy: employee => employee.role,
  collapsedGroups: collapsed.value,
  onToggleGroup: toggleGroup,
  getRowKey: employee => employee.id,
  groupOrder: ['Designer', 'Engineer'],
}))

const numbers = useTableRowIndex<Employee>(() => ({
  data: groupedRows.value,
  getRowKey: employee => employee.id,
}))
</script>

<template>
  <Table
    :data="groupedRows"
    :columns="columns"
    :id-key="idKey"
    :plugins="{ grouped: groupedPlugin, numbers }"
  />
</template>
```

## Parameters

`useTableGroupedRows()` takes one config object. Wrap it in a getter or a `ref` so the sections follow the current rows and the current collapsed set: `useTableGroupedRows(() => ({ ... }))`.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `readonly T[]` | — | Required. The flat data to group. |
| `groupBy` | `(item: T) => string` | — | Required. Derive the group key for a row. Rows with the same key share a section. |
| `collapsedGroups` | `ReadonlySet<string>` | — | Required. Set of currently-collapsed group keys. |
| `onToggleGroup` | `(groupKey: string) => void` | — | Required. Called with a group key when its header is toggled. |
| `renderGroupHeader` | `(groupKey: string, count: number, collapsed: boolean) => VNodeChild` | — | Custom renderer for a group header's content, right of the chevron. Defaults to `groupKey (count)`. |
| `getRowKey` | `(item: T) => string` | — | Stable key for a real row. Falls back to a positional key when omitted. |
| `groupOrder` | `readonly string[]` | — | Explicit group ordering. Groups not listed keep first-seen order after these. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `data` | `ComputedRef<T[]>` | The rows plus one section header per group, in display order. A collapsed group keeps its header and drops its rows. |
| `plugin` | `TablePlugin` | Makes a section header span the row, skips it for selection and row numbers, and toggles it on click. |
| `idKey` | `(item: T) => string` | Row identity for both kinds of row: a section header answers with its group key, a data row with `getRowKey` or its position. |

### isTableGroupHeader

`isTableGroupHeader(item)` narrows an item of `data` to a section header and gives you `groupKey` and `count` without a cast. It is the same check the plugin uses.

## Section header fields

| Name | Type | Description |
| --- | --- | --- |
| `groupKey` | `string` | The key the section was grouped by. |
| `count` | `number` | How many rows the group holds, whether or not it is open. |
| `id` | `string` | Stable id of the section header, built from the group key. |
