diff --git a/src/FillSpace.jsx b/src/FillSpace.jsx index 724e661..5262e0e 100644 --- a/src/FillSpace.jsx +++ b/src/FillSpace.jsx @@ -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 ; +} + 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 { ))} + {probe != null ? ( + { + this.probeRef = el; + }} + aria-hidden + sx={{ + position: 'absolute', + left: 0, + top: 0, + width: '100%', + visibility: 'hidden', + pointerEvents: 'none', + }} + > + + + {probe} + + + + ) : null} {debug ? (