leaderboard: gallery scroll box sized off screen.availHeight
Browse filesVerified the real cause with a headless harness reproducing HF's nested
iframes: HF auto-resizes the Space iframe to full content height, so
inside the gallery window.innerHeight / parent.innerHeight / vh are all
inflated to the content height (live: 1642 vs a 900 viewport) and only
the cross-origin top window scrolls -- which is why no inner cap or
sticky-against-the-iframe ever worked and the GT row scrolled away.
screen.availHeight is the one viewport-ish measure that survives the
nesting, so size the gallery's own scroll box from it (availHeight minus
a chrome reserve, clamped). The column header + GT row are sticky inside
that box, so they stay locked while the submission rows scroll, and the
box is the single scroller (harness confirms no outer-page overflow
across laptop/desktop/large screens).
Co-authored-by: Cursor <cursoragent@cursor.com>
- gallery.py +43 -25
|
@@ -250,7 +250,18 @@ body {
|
|
| 250 |
.section-caption { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
|
| 251 |
.section-caption b { color: var(--ink); font-weight: 600; }
|
| 252 |
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
.grid-head, .grow {
|
| 255 |
display: grid;
|
| 256 |
grid-template-columns: 52px minmax(200px, 1.3fr) 160px repeat(var(--ncol, 4), minmax(140px, 1fr));
|
|
@@ -574,39 +585,46 @@ function syncHeadHeight() {
|
|
| 574 |
if (head) document.documentElement.style.setProperty('--head-h', head.offsetHeight + 'px');
|
| 575 |
}
|
| 576 |
|
| 577 |
-
//
|
| 578 |
-
//
|
| 579 |
-
//
|
| 580 |
-
//
|
| 581 |
-
//
|
| 582 |
-
//
|
| 583 |
-
//
|
| 584 |
-
//
|
| 585 |
-
//
|
| 586 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
function fitIframe() {
|
| 588 |
try {
|
| 589 |
const fe = window.frameElement;
|
| 590 |
-
if (
|
| 591 |
-
const content = Math.ceil(document.body.scrollHeight);
|
| 592 |
-
let target = content;
|
| 593 |
-
const pv = window.parent;
|
| 594 |
-
if (pv && pv.innerHeight) {
|
| 595 |
-
const top = fe.getBoundingClientRect().top; // iframe top in viewport
|
| 596 |
-
const avail = Math.floor(pv.innerHeight - top - BOTTOM_RESERVE);
|
| 597 |
-
if (avail > 240) target = Math.min(content, avail);
|
| 598 |
-
}
|
| 599 |
-
fe.style.height = target + 'px';
|
| 600 |
} catch (e) { /* sandboxed -> keep fallback height */ }
|
| 601 |
}
|
| 602 |
|
| 603 |
buildGallery();
|
|
|
|
| 604 |
fitIframe();
|
| 605 |
-
function relayout() { syncHeadHeight(); fitIframe(); }
|
| 606 |
window.addEventListener('resize', relayout);
|
| 607 |
-
// The viewport height can change without the iframe's own width changing
|
| 608 |
-
// (e.g. browser window resized shorter), so also listen on the parent.
|
| 609 |
-
try { window.parent.addEventListener('resize', relayout); } catch (e) { /* ignore */ }
|
| 610 |
if (window.ResizeObserver) new ResizeObserver(fitIframe).observe(document.body);
|
| 611 |
if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
|
| 612 |
"""
|
|
|
|
| 250 |
.section-caption { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
|
| 251 |
.section-caption b { color: var(--ink); font-weight: 600; }
|
| 252 |
|
| 253 |
+
/* The gallery is its own scroll container so the column header + ground-truth
|
| 254 |
+
row (both `position: sticky`) stay locked at the top while the submission
|
| 255 |
+
rows scroll inside it. This is the ONLY scroller for the rows -- it must not
|
| 256 |
+
leak out to the host page. Height is set in JS from `screen.availHeight`
|
| 257 |
+
(the one viewport-ish measure that survives HF's nested iframes, which
|
| 258 |
+
inflate `innerHeight`/`vh` to the full content height); the px value here is
|
| 259 |
+
the pre-script fallback. */
|
| 260 |
+
.gallery {
|
| 261 |
+
background: var(--panel); border: 1px solid var(--line);
|
| 262 |
+
border-radius: var(--radius); box-shadow: var(--shadow); position: relative;
|
| 263 |
+
max-height: var(--gallery-max, 560px); overflow-y: auto; overflow-x: hidden;
|
| 264 |
+
}
|
| 265 |
.grid-head, .grow {
|
| 266 |
display: grid;
|
| 267 |
grid-template-columns: 52px minmax(200px, 1.3fr) 160px repeat(var(--ncol, 4), minmax(140px, 1fr));
|
|
|
|
| 585 |
if (head) document.documentElement.style.setProperty('--head-h', head.offsetHeight + 'px');
|
| 586 |
}
|
| 587 |
|
| 588 |
+
// Height of the gallery scroll box. HF auto-resizes the Space iframe to its
|
| 589 |
+
// full content height, so `window.innerHeight` / `vh` inside these nested
|
| 590 |
+
// iframes report the inflated content height, not the real viewport -- they
|
| 591 |
+
// can't be used to size a one-screen box. `screen.availHeight` is the screen
|
| 592 |
+
// work-area and is NOT affected by the iframe nesting, so we derive the box
|
| 593 |
+
// height from it (a fraction, clamped) and the rows scroll inside the box while
|
| 594 |
+
// the sticky header + ground-truth row stay locked.
|
| 595 |
+
// Reserve for everything that is NOT the scroll box but still has to fit on
|
| 596 |
+
// screen: the browser/OS chrome between the screen work-area and the window
|
| 597 |
+
// viewport, plus the HF page header + Gradio title/tabs + this page's caption
|
| 598 |
+
// and Refresh button. Subtracting it from the screen height keeps the whole
|
| 599 |
+
// gallery within one viewport, so there is a single scrollbar (the box's own)
|
| 600 |
+
// rather than the box plus an outer page scroll. Deliberately generous: a box
|
| 601 |
+
// that is a little short (a touch more in-box scrolling) is far better than a
|
| 602 |
+
// confusing second scrollbar.
|
| 603 |
+
var CHROME_RESERVE = 560;
|
| 604 |
+
function sizeGalleryBox() {
|
| 605 |
+
try {
|
| 606 |
+
const avail = (window.screen && window.screen.availHeight) || 900;
|
| 607 |
+
const h = Math.max(300, Math.min(760, Math.round(avail - CHROME_RESERVE)));
|
| 608 |
+
document.documentElement.style.setProperty('--gallery-max', h + 'px');
|
| 609 |
+
} catch (e) { /* keep CSS fallback */ }
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
// With the gallery box capped, the page content (caption + box) is bounded, so
|
| 613 |
+
// sizing the iframe to it keeps the iframe from adding a second scrollbar: the
|
| 614 |
+
// gallery's own box is the single scroller for the rows. No-ops if frameElement
|
| 615 |
+
// is unreadable.
|
| 616 |
function fitIframe() {
|
| 617 |
try {
|
| 618 |
const fe = window.frameElement;
|
| 619 |
+
if (fe) fe.style.height = Math.ceil(document.body.scrollHeight) + 'px';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
} catch (e) { /* sandboxed -> keep fallback height */ }
|
| 621 |
}
|
| 622 |
|
| 623 |
buildGallery();
|
| 624 |
+
sizeGalleryBox();
|
| 625 |
fitIframe();
|
| 626 |
+
function relayout() { syncHeadHeight(); sizeGalleryBox(); fitIframe(); }
|
| 627 |
window.addEventListener('resize', relayout);
|
|
|
|
|
|
|
|
|
|
| 628 |
if (window.ResizeObserver) new ResizeObserver(fitIframe).observe(document.body);
|
| 629 |
if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
|
| 630 |
"""
|