Skip to content

Latest commit

 

History

History
24 lines (16 loc) · 7.12 KB

File metadata and controls

24 lines (16 loc) · 7.12 KB

Pagination

pageSize: 0 disables pagination — all rows are returned on a single page. All four adapters default to 0. processedData holds all filtered/sorted rows regardless of pagination (used for toggleSelectAll, total count, etc.).

computeTotalPages/paginateData (core) treat a non-finite pageSize (e.g. NaN, from an unvalidated input forwarded straight to setPageSize) the same as pageSize <= 0 — pagination disabled, all rows returned — rather than propagating NaN into Math.ceil/Array.prototype.slice math, which would otherwise silently collapse the table to an empty page (slice(NaN, NaN) coerces both bounds to 0). paginateData similarly falls back page itself to 1 when it's non-finite. React/Vue/Solid's setPage/setPageSize actions guard at the call boundary too — a non-finite argument is a no-op (state is left untouched) rather than being stored and then relied on to be caught downstream.

Grouping and pagination compose as: group the full processedData → flatten to visible items (getVisibleRows, respecting collapse state) → paginate that flattened header+row sequence → re-chunk the page's slice back into per-group form for rendering. This is a deliberate reversal of the more obvious "paginate data rows, then group whatever lands on that page" order — that order would let a page silently render pageSize data rows plus however many header rows happened to land on top of them, so a pageSize: 20 table with grouping active could visibly render 23+ rows. Counting header rows against the same budget as data rows means a page never renders more than pageSize rows total, at the cost of a group's rows now being able to split across a page boundary purely because the budget ran out mid-group (this already could happen in a milder form before — grouping was never sort-stable across the old pagination boundary either, so the same group value could already appear as two separate header occurrences on two different pages).

  • paginateVisibleGroups(groupedFull, visibleItems, collapsedGroups, defaultCollapsed, page, pageSize) (core) is the re-chunking step, returning PagedGroup<TRow>[] — a GroupResult plus continued: boolean and sampleRow: TRow | undefined. It's the inverse of getVisibleRows, scoped to one page: paginateData (generic enough to reuse as-is for VisibleItem<TRow>[]) slices visibleItems for the page, then a single pass rebuilds group chunks from that slice.
    • continued is true when a chunk's header is a repeat — the group's real header rendered on an earlier page and this page's slice starts mid-group (a 'row' item whose groupKey doesn't match the currently-open chunk). Each adapter renders a continued chunk's header identically to a real one but appends L.groupContinued (e.g. "(cont'd)") next to the row count, so a returning header reads as a continuation rather than an unrelated duplicate group.
    • A collapsed group's rows are backfilled from the full group (groupedFull) rather than accumulated from the page slice, because a collapsed group contributes exactly one item to visibleItems (its header, no rows) — it never enters the paginated flow at all, so there's no "this page's portion" of it to fall back to. This also happens to be the more useful answer: collapsing a group is usually precisely so its aggregate/select-all reflect the whole group, not whatever fraction of it shares the current page. An expanded group's rows, by contrast, really is just the chunk visible on this page — consistent with the pre-existing behavior that a group's aggregate was never a true full-dataset total, only ever scoped to whatever page/slice it appeared in.
    • sampleRow is a representative row for the group (its first row in groupedFull, or the row itself for an ungrouped chunk), always present when key is non-null — independent of whether rows happens to be empty for this specific page occurrence. This matters because a header can legitimately be the very last item of a page's budget with zero of its own rows following until the next page (an expanded group whose first row is pushed to the next page); rendering the header's groupBy value (via the column's real accessor/formatter) and the aggregate row's col.format(v, row) both need some row to read from, and rows[0] would be undefined in exactly this case. Every adapter reads sampleRow (never rows[0]) for these two things, while rows itself remains correct for the checkbox/aggregate-computation purposes above.
  • paginateVisibleItems(visibleItems, page, pageSize) (core) is the equivalent per-page slice for keyboard navigation (see Keyboard navigation): a plain paginateData slice, except when that slice starts with row items whose group header rendered on an earlier page — then a synthetic { kind: 'group', key } item is prepended so the repeated ("continued") header, a real focusable row once rendered, is a valid Tab stop here too.
  • pagedData (still exposed from useTableState/createTableState, unchanged type TRow[]) is now derived from the same paginated flattening — paginateData(visibleItems, page, pageSize) filtered to 'row' items and mapped to .row — rather than a flat data-row slice, so it reflects the data rows actually visible on the page (fewer than pageSize whenever headers share the budget), not a fixed-size chunk of processedData.
  • numPages is computeTotalPages(visibleItems.length, pageSize) — visibleItems.length (headers + visible rows) rather than processedData.length, so it grows when groups are expanded and shrinks when they're collapsed. Toggling a group's collapse state can therefore change numPages out from under the current page; this is handled the same way a filter change already could shrink numPages — no state reset on toggle, just the pre-existing Math.min(page, numPages) clamp applied wherever page is used.
  • The toolbar's group count stat dedupes groupedData by key before counting (new Set(groupedData.map(g => g.key)).size) rather than using groupedData.length directly, since a split group's continued chunk would otherwise be counted as a second group.

The «, ‹, › and » buttons are named by the firstPage/previousPage/nextPage/lastPage labels (aria-label), since the glyph alone reads as nothing useful.

The "Rows per page" <select>'s options are hardcoded to [10, 20, 50, 100] in each adapter, but a consumer's initialViewState.pageSize/setPageSize isn't restricted to those four — a plain <select> bound to a value absent from its own <option>s can't select anything, so the browser falls back to displaying the first option, silently showing the wrong number while the actual pageSize (and the row count on screen) stays correct. mergePageSizeOptions(options, pageSize) (core) inserts pageSize into the option list (sorted) when it's missing, so the dropdown always has a matching option; each adapter calls it with its own [10, 20, 50, 100] base list rather than rendering it directly.


Part of the @vates/data-table architecture docs — see CLAUDE.md for the project overview.