u
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import List from '@mui/material/List';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
@@ -21,8 +22,8 @@ import { alpha } from '@mui/material/styles';
|
||||
* @prop {(count: number) => void} [onMeasurement] - Called whenever the number of `measureElement` units that would fit in the whole spacer height changes, as if there were no `children` (i.e. the total row count the space could hold). Fires once after mount with the initial count.
|
||||
* @prop {number} [minHeight=0] - Minimum spacer height in px.
|
||||
* @prop {number} [minCount=0] - Minimum number of `measureElement` units the spacer must fit. When the viewport is too small, the spacer grows beyond the viewport so the page scrolls instead of clipping.
|
||||
* @prop {Array} [items] - When provided, FillSpace paginates: it measures the page size, slices `items`, renders the current page via `renderPage`, and owns wheel-pagination plus the pagination header. Omit for legacy children mode.
|
||||
* @prop {(pageItems: Array, page: {offset:number, pageSize:number, currentPage:number, pageCount:number, total:number}) => ReactNode} [renderPage] - Renders the current page slice (paginated mode).
|
||||
* @prop {Array} [items] - When provided with `renderItem`, FillSpace paginates: it measures the page size from the rendered row height, slices `items`, renders the current page as a list, and owns wheel-pagination plus the pagination header. Omit for legacy children mode.
|
||||
* @prop {(item: any, index: number) => ReactNode} [renderItem] - Renders one row element (e.g. a brand row). The first rendered row doubles as the measure element, so no separate `measureElement` is needed in paginated mode.
|
||||
* @prop {number} [minPageSize=10] - Minimum rows per page in paginated mode.
|
||||
* @prop {(page: number) => void} [onPageChange] - Called when the current page changes (wheel or header).
|
||||
* @prop {number} [maxHeight] - Optional maximum spacer height in px.
|
||||
@@ -91,7 +92,8 @@ class FillSpace extends Component {
|
||||
prevProps.minPageSize !== this.props.minPageSize ||
|
||||
prevProps.maxHeight !== this.props.maxHeight ||
|
||||
prevProps.measureElement !== this.props.measureElement ||
|
||||
prevProps.measureGap !== this.props.measureGap
|
||||
prevProps.measureGap !== this.props.measureGap ||
|
||||
prevProps.items !== this.props.items
|
||||
) {
|
||||
this._observe();
|
||||
this._measure();
|
||||
@@ -171,6 +173,7 @@ class FillSpace extends Component {
|
||||
containerRef,
|
||||
reserveBottom = 0,
|
||||
measureElement,
|
||||
renderItem,
|
||||
measureGap = 0,
|
||||
minHeight = 0,
|
||||
minCount = 0,
|
||||
@@ -223,18 +226,23 @@ class FillSpace extends Component {
|
||||
contentHeight = this.contentRef.offsetHeight;
|
||||
}
|
||||
|
||||
// Measure the height of one measurement unit from the hidden copy so the
|
||||
// repeats match the real rendered size.
|
||||
// Unit height: in paginated mode the first rendered row is the measure
|
||||
// element — no separate hidden sample needed. Legacy children mode
|
||||
// still uses the hidden `measureElement` copy.
|
||||
const paginated = Array.isArray(items) && typeof renderItem === 'function';
|
||||
let unitHeight = this.state.unitHeight;
|
||||
if (measureElement != null && this.unitRef) {
|
||||
if (paginated) {
|
||||
const firstRow = this.contentRef?.querySelector?.('[data-fill-row]') ?? this.contentRef?.firstElementChild;
|
||||
const h = firstRow?.offsetHeight;
|
||||
if (h > 0) unitHeight = h;
|
||||
if (!(unitHeight > 0) && measureElement != null && this.unitRef) {
|
||||
const fallback = this.unitRef.offsetHeight;
|
||||
if (fallback > 0) unitHeight = fallback;
|
||||
}
|
||||
} else if (measureElement != null && this.unitRef) {
|
||||
const h = this.unitRef.offsetHeight;
|
||||
if (h > 0) unitHeight = h;
|
||||
}
|
||||
|
||||
// Minimum rows: grow past the viewport so the page scrolls instead
|
||||
// of clipping when the window is too small. Paginated mode uses
|
||||
// minPageSize (fallback minCount).
|
||||
const paginated = Array.isArray(items);
|
||||
const minSize = Math.max(1, Number(minPageSize ?? minCount) || 10);
|
||||
const effectiveMinCount = paginated ? Math.max(Number(minCount) || 0, minSize) : Number(minCount) || 0;
|
||||
const minHeightForCount = minHeightFromCount(effectiveMinCount, unitHeight, measureGap);
|
||||
@@ -243,14 +251,14 @@ class FillSpace extends Component {
|
||||
maxHeight ?? Infinity,
|
||||
);
|
||||
|
||||
// How many units would fit in the WHOLE spacer height, as if there were
|
||||
// no children — this is what onMeasurement reports.
|
||||
// How many units fit in the spacer height — drives the page size in
|
||||
// paginated mode and the `onMeasurement` callback in legacy mode.
|
||||
let totalCount = 0;
|
||||
// How many units actually fit in the leftover space below the children —
|
||||
// this is what gets rendered. Starts at 0: if the content already fills
|
||||
// the space, none fit.
|
||||
let fillCount = 0;
|
||||
if (measureElement != null && unitHeight > 0) {
|
||||
if (unitHeight > 0 && (measureElement != null || paginated)) {
|
||||
totalCount = Math.floor((finalHeight + measureGap) / (unitHeight + measureGap));
|
||||
const fillHeight = Math.max(0, finalHeight - contentHeight);
|
||||
fillCount = Math.floor((fillHeight + measureGap) / (unitHeight + measureGap));
|
||||
@@ -269,6 +277,7 @@ class FillSpace extends Component {
|
||||
}
|
||||
|
||||
// Paginated mode: fit count drives the page size (min minPageSize).
|
||||
// Legacy mode only forwards the count via onMeasurement.
|
||||
let nextPageSize = this.state.pageSize;
|
||||
let measured = this.state.measured;
|
||||
if (paginated && totalCount > 0) {
|
||||
@@ -349,22 +358,21 @@ class FillSpace extends Component {
|
||||
debug = false,
|
||||
children,
|
||||
items,
|
||||
renderPage,
|
||||
renderItem,
|
||||
measureElement,
|
||||
measureGap = 0,
|
||||
sx,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
// Paginated mode: FillSpace owns page size (from fit), current page,
|
||||
// wheel-pagination and the pagination header. Caller just feeds `items`
|
||||
// and renders the slice via `renderPage`.
|
||||
if (Array.isArray(items) && typeof renderPage === 'function') {
|
||||
// Paginated mode: FillSpace owns page size (from the rendered row
|
||||
// height), current page, wheel-pagination and the pagination header.
|
||||
// Caller feeds `items` plus one row element via `renderItem`.
|
||||
if (Array.isArray(items) && typeof renderItem === 'function') {
|
||||
return this.renderPaginated({
|
||||
minHeight,
|
||||
maxHeight,
|
||||
debug,
|
||||
measureElement,
|
||||
measureGap,
|
||||
sx,
|
||||
rest,
|
||||
@@ -473,11 +481,12 @@ class FillSpace extends Component {
|
||||
|
||||
/**
|
||||
* Paginated mode: pagination header (hidden until measured) + exactly one
|
||||
* page of `items` rendered via `renderPage`. Wheel over the list flips
|
||||
* pages; effort scales with page count (slow for few pages).
|
||||
* page of `items`, each row rendered via `renderItem`. The first row
|
||||
* doubles as the measure element. Wheel over the list flips pages;
|
||||
* effort scales with page count (slow for few pages).
|
||||
*/
|
||||
renderPaginated({ minHeight, maxHeight, debug, measureElement, measureGap, sx, rest }) {
|
||||
const { items, renderPage, onWheel } = this.props;
|
||||
renderPaginated({ minHeight, maxHeight, debug, measureGap, sx, rest }) {
|
||||
const { items, renderItem, onWheel } = this.props;
|
||||
const { height, pageSize, currentPage, measured } = this.state;
|
||||
const total = items.length;
|
||||
const pageCount = Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
|
||||
@@ -562,26 +571,14 @@ class FillSpace extends Component {
|
||||
}}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{renderPage(pageItems, { offset, pageSize, currentPage: safePage, pageCount, total })}
|
||||
<List dense disablePadding sx={{ width: '100%' }}>
|
||||
{pageItems.map((item, i) => (
|
||||
<Box key={item?.id ?? offset + i} data-fill-row sx={{ width: '100%' }}>
|
||||
{renderItem(item, offset + i)}
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
{measureElement != null ? (
|
||||
<Box
|
||||
ref={(el) => {
|
||||
this.unitRef = el;
|
||||
}}
|
||||
aria-hidden
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
visibility: 'hidden',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
{measureElement}
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
{debug ? (
|
||||
<Box
|
||||
|
||||
Reference in New Issue
Block a user