This commit is contained in:
seb
2026-10-09 23:27:49 +02:00
parent 41079f3057
commit 91ef0c2567

View File

@@ -54,6 +54,21 @@ function renderRowElement(renderItem, item, index) {
return renderItem(item, index);
}
/**
* Data-free probe of the row height, rendered hidden. Class components
* (e.g. `renderItem={ArtikelMarke}`) tolerate empty props
* (`brand`/`item` undefined) and keep a fixed height (40px thumb /
* text field), so the probe measures the row without depending on the
* first data element. Plain callbacks are NOT probed — calling arbitrary
* user code with `undefined` could throw — they fall back to the first
* rendered data row.
*/
function renderProbeElement(renderItem) {
if (!isRowComponent(renderItem)) return null;
const Row = renderItem;
return <Row brand={undefined} item={undefined} index={0} disabled />;
}
class FillSpace extends Component {
constructor(props) {
super(props);
@@ -74,6 +89,7 @@ class FillSpace extends Component {
this.ref = null;
this.contentRef = null;
this.unitRef = null;
this.probeRef = null;
this.paginationRef = null;
this.wheelAcc = 0;
this.observer = null;
@@ -232,13 +248,19 @@ class FillSpace extends Component {
contentHeight = this.contentRef.offsetHeight;
}
// Paginated: first rendered row is the measure element. Legacy: hidden copy.
// Paginated: hidden probe rendered from `renderItem` itself (no data).
// Legacy: hidden copy.
const paginated = Array.isArray(items) && typeof renderItem === 'function';
let unitHeight = this.state.unitHeight;
if (paginated) {
const firstRow = this.contentRef?.querySelector?.('[data-fill-row]') ?? this.contentRef?.firstElementChild;
const h = firstRow?.offsetHeight;
if (h > 0) unitHeight = h;
const probeH = this.probeRef?.offsetHeight;
if (probeH > 0) {
unitHeight = probeH;
} else {
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;
@@ -476,7 +498,7 @@ class FillSpace extends Component {
/** Pagination header + one page of rows. Wheel over the list flips pages. */
renderPaginated({ minHeight, maxHeight, debug, measureGap, sx, rest }) {
const { items, renderItem, onWheel } = this.props;
const { items, renderItem, measureElement, 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)));
@@ -485,6 +507,9 @@ class FillSpace extends Component {
const pageItems = items.slice(offset, offset + pageSize);
// Hidden until the fit is measured — no "1–10" flash on load.
const showPagination = measured && total > pageSize;
// Hidden data-free probe: measures `renderItem` itself (no data), so
// the first pass already uses the final row height — one pass, no flip.
const probe = renderProbeElement(renderItem) ?? measureElement;
return (
<>
@@ -588,6 +613,28 @@ class FillSpace extends Component {
))}
</List>
</Box>
{probe != null ? (
<Box
ref={(el) => {
this.probeRef = el;
}}
aria-hidden
sx={{
position: 'absolute',
left: 0,
top: 0,
width: '100%',
visibility: 'hidden',
pointerEvents: 'none',
}}
>
<List dense disablePadding sx={{ width: '100%' }}>
<Box data-fill-row sx={{ width: '100%' }}>
{probe}
</Box>
</List>
</Box>
) : null}
</Box>
{debug ? (
<Box