diff --git a/src/gui/components/_legacy.py b/src/gui/components/_legacy.py index d364f9c..28bf1a8 100644 --- a/src/gui/components/_legacy.py +++ b/src/gui/components/_legacy.py @@ -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 ```` + 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; +} """, 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';