fix(footer): restore soft-nav for Close (no page reload on shutdown)
Footer Close was using ``<a href="./close">`` which triggers a
browser hard-nav. That's a visible page-reload flash, websocket
churn, and slower shutdown than the previous sidebar Close —
which used ``st.navigation``'s soft nav.
Restore the soft-nav path:
- ``render_sticky_footer`` now renders a hidden ``st.page_link``
pointing at ``pages/99_Close.py``. Positioned off-screen via
CSS (``stElementContainer:has(a[data-testid=stPageLink]
[href$=/close])``) so it occupies no layout space but stays in
the DOM, reachable + clickable.
- Footer's Close <button> click handler now dispatches a
programmatic click on that hidden page_link. Streamlit's React
handler picks it up and runs the soft nav (same code path the
old sidebar entry used). Falls back to ``window.location.href``
if the helper link hasn't rendered yet so the button is never
a no-op.
- The page_link call is wrapped in try/except: ``AppTest`` doesn't
populate the page-nav session keys it needs and raises
``KeyError('url_pathname')``. Failure costs only the soft-nav
optimization — Close still works via the hard-nav fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -728,11 +728,59 @@ def render_sticky_footer() -> None:
|
||||
#datatools-help-popover .dt-help-dismiss:hover {
|
||||
color: rgb(38, 39, 48) !important;
|
||||
}
|
||||
/* Hide the sticky-footer's helper st.page_link off-screen but
|
||||
keep it in the DOM + clickable. The footer's Close button
|
||||
dispatches a programmatic click on this link so navigation uses
|
||||
Streamlit's soft nav (preserves the websocket, no visible page
|
||||
reload) instead of the browser hard-nav an ``<a href="./close">``
|
||||
would trigger. Off-screen (rather than ``display:none``) so
|
||||
React event delegation works reliably across browsers. */
|
||||
[data-testid="stElementContainer"]:has(a[data-testid="stPageLink"][href$="/close"]),
|
||||
[data-testid="stElementContainer"]:has(a[data-testid="stPageLink"][href$="/close/"]) {
|
||||
position: absolute !important;
|
||||
left: -9999px !important;
|
||||
top: -9999px !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
overflow: hidden !important;
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
/* Defensive fallback for browsers without :has() — at least
|
||||
shrink the inline page_link so it doesn't render a visible row. */
|
||||
a[data-testid="stPageLink"][href$="/close"],
|
||||
a[data-testid="stPageLink"][href$="/close/"] {
|
||||
visibility: hidden !important;
|
||||
height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# Hidden Streamlit page_link to the close page. The footer's
|
||||
# Close button programmatically clicks the anchor this renders,
|
||||
# which triggers Streamlit's soft navigation (same code path
|
||||
# the previous sidebar Close entry used). The link is positioned
|
||||
# off-screen via the CSS above so it doesn't take page space
|
||||
# but remains reachable to the JS click dispatch.
|
||||
#
|
||||
# Wrapped because ``st.page_link`` raises ``KeyError('url_pathname')``
|
||||
# under ``AppTest`` (the test harness does not populate the page-nav
|
||||
# session keys ``page_link`` needs to mark itself active/inactive).
|
||||
# The JS click handler has a hard-nav fallback when this helper
|
||||
# link isn't present, so a failure here only costs the soft-nav
|
||||
# optimization — Close still works.
|
||||
try:
|
||||
st.page_link(
|
||||
"pages/99_Close.py",
|
||||
label=_t("footer.close"),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from streamlit.components.v1 import html as _components_html
|
||||
_components_html(
|
||||
f"""
|
||||
@@ -757,14 +805,32 @@ def render_sticky_footer() -> None:
|
||||
helpBtn.className = 'datatools-footer-btn help';
|
||||
helpBtn.textContent = labels.help;
|
||||
|
||||
var closeLink = doc.createElement('a');
|
||||
closeLink.className = 'datatools-footer-btn close';
|
||||
closeLink.href = './close';
|
||||
closeLink.target = '_self';
|
||||
closeLink.textContent = labels.close;
|
||||
var closeBtn = doc.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.className = 'datatools-footer-btn close';
|
||||
closeBtn.textContent = labels.close;
|
||||
// Soft-nav via the hidden ``st.page_link`` that
|
||||
// ``render_sticky_footer`` injects. Streamlit owns its click
|
||||
// handler and will route through ``st.switch_page`` (same
|
||||
// code path the old sidebar Close entry used) — no full-page
|
||||
// reload, no websocket churn. Fall back to a hard nav if the
|
||||
// helper link hasn't rendered yet (first paint race) so the
|
||||
// button is never a no-op.
|
||||
closeBtn.addEventListener('click', function (e) {{
|
||||
e.preventDefault();
|
||||
var helper = doc.querySelector(
|
||||
'a[data-testid="stPageLink"][href$="/close"], ' +
|
||||
'a[data-testid="stPageLink"][href$="/close/"]'
|
||||
);
|
||||
if (helper) {{
|
||||
helper.click();
|
||||
}} else {{
|
||||
window.location.href = './close';
|
||||
}}
|
||||
}});
|
||||
|
||||
div.appendChild(helpBtn);
|
||||
div.appendChild(closeLink);
|
||||
div.appendChild(closeBtn);
|
||||
|
||||
var pop = doc.createElement('div');
|
||||
pop.id = 'datatools-help-popover';
|
||||
|
||||
Reference in New Issue
Block a user