This commit is contained in:
seb
2026-10-11 12:49:16 +02:00
parent 266f5212aa
commit 8325402bc6

View File

@@ -55,10 +55,12 @@ function renderProbeElement(renderItem) {
class FillSpace extends Component {
constructor(props) {
super(props);
const minSize = Math.max(1, Number(props.minPageSize) || 10);
const maxSize = Number(props.maxPageSize) > 0 ? Number(props.maxPageSize) : Infinity;
this.state = {
unitHeight: 0,
viewportHeight: null,
pageSize: Number(props.minPageSize) || 10,
pageSize: Math.min(maxSize, minSize),
currentPage: 1,
};
this.ref = null;
@@ -79,8 +81,13 @@ class FillSpace extends Component {
if (prevProps.renderItem !== this.props.renderItem) {
this._probeMeasure();
}
if (prevProps.minPageSize !== this.props.minPageSize) {
this.setState({ pageSize: Number(this.props.minPageSize) || 10 });
if (
prevProps.minPageSize !== this.props.minPageSize ||
prevProps.maxPageSize !== this.props.maxPageSize
) {
const minSize = Math.max(1, Number(this.props.minPageSize) || 10);
const maxSize = Number(this.props.maxPageSize) > 0 ? Number(this.props.maxPageSize) : Infinity;
this.setState({ pageSize: Math.min(maxSize, minSize) });
}
if (
prevProps.aboveRef !== this.props.aboveRef ||
@@ -165,7 +172,8 @@ class FillSpace extends Component {
/** Only measures the height of the `renderItem` probe. Nothing else. */
_probeMeasure() {
const h = this.probeRef?.offsetHeight;
const firstRow = this.ref?.querySelector('[data-fill-row]');
const h = this.probeRef?.offsetHeight || firstRow?.offsetHeight;
if (h > 0 && h !== this.state.unitHeight) {
this.setState({ unitHeight: h }, () => this._viewportMeasure());
}
@@ -197,14 +205,20 @@ class FillSpace extends Component {
available = Math.max(0, window.innerHeight - rect.top - belowPx);
}
const minSize = Math.max(1, Number(minPageSize) || 10);
const maxSize = Number(this.props.maxPageSize) > 0 ? Number(this.props.maxPageSize) : Infinity;
const { unitHeight } = this.state;
const nextPageSize =
const calculated =
unitHeight > 0 ? Math.max(minSize, Math.floor(available / unitHeight)) : minSize;
const nextPageSize = Math.min(maxSize, calculated);
const nextViewportHeight =
Number.isFinite(maxSize) && unitHeight > 0
? Math.min(available, nextPageSize * unitHeight)
: available;
if (
available !== this.state.viewportHeight ||
nextViewportHeight !== this.state.viewportHeight ||
nextPageSize !== this.state.pageSize
) {
this.setState({ viewportHeight: available, pageSize: nextPageSize });
this.setState({ viewportHeight: nextViewportHeight, pageSize: nextPageSize });
}
}
@@ -250,6 +264,8 @@ class FillSpace extends Component {
minHeight,
maxHeight,
minPageSize,
maxPageSize,
listRef,
onPageChange,
onWheel,
debug = false,
@@ -275,60 +291,54 @@ class FillSpace extends Component {
return (
<>
<Box
// Fixed single-line bar: the header height must not depend on the
// page count. Otherwise the first fit (e.g. "1-17") pushes the
// spacer down, the available height shrinks, and a second pass
// flips to "1-15". One pass: constant 44px, real values always
// rendered (only visibility is gated), no placeholder numbers.
sx={{ mb: 1, height: 44, overflow: 'hidden' }}
>
{showPagination ? (
<Box
aria-hidden={!showPagination || undefined}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'nowrap',
gap: 1,
width: '100%',
height: 44,
visibility: showPagination ? 'visible' : 'hidden',
pointerEvents: showPagination ? 'auto' : 'none',
}}
sx={{ mb: 1, height: 44, overflow: 'hidden' }}
>
<Typography
variant="body2"
color="text.secondary"
noWrap
sx={{ flexShrink: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{total === 0 ? '0' : `${offset + 1}–${Math.min(offset + pageSize, total)}`} von{' '}
{total}
</Typography>
<Pagination
count={pageCount}
page={safePage}
onChange={(_e, page) => this._goToPage(page)}
color="primary"
size="large"
onMouseDown={(e) => e.preventDefault()}
<Box
sx={{
flexShrink: 0,
maxWidth: '100%',
overflowX: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': { display: 'none' },
'& .MuiPagination-ul': { flexWrap: 'nowrap' },
'& .MuiPaginationItem-root': {
minWidth: 44,
height: 44,
fontSize: '1rem',
},
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'nowrap',
gap: 1,
width: '100%',
height: 44,
}}
/>
>
<Typography
variant="body2"
color="text.secondary"
noWrap
sx={{ flexShrink: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{total === 0 ? '0' : `${offset + 1}–${Math.min(offset + pageSize, total)}`} von{' '}
{total}
</Typography>
<Pagination
count={pageCount}
page={safePage}
onChange={(_e, page) => this._goToPage(page)}
color="primary"
size="large"
onMouseDown={(e) => e.preventDefault()}
sx={{
flexShrink: 0,
maxWidth: '100%',
overflowX: 'auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': { display: 'none' },
'& .MuiPagination-ul': { flexWrap: 'nowrap' },
'& .MuiPaginationItem-root': {
minWidth: 44,
height: 44,
fontSize: '1rem',
},
}}
/>
</Box>
</Box>
</Box>
) : null}
<Box
ref={(el) => {
this.ref = el;
@@ -357,7 +367,7 @@ class FillSpace extends Component {
]}
>
<Box sx={{ width: '100%' }}>
<List dense disablePadding sx={{ width: '100%' }}>
<List ref={listRef} dense disablePadding sx={{ width: '100%' }}>
{pageItems.map((item, i) => (
<Box key={item?.id ?? offset + i} data-fill-row sx={{ width: '100%' }}>
{renderRowElement(renderItem, item, offset + i)}