Files
sure/app/javascript/controllers/settings_scroll_controller.js
Guillem Arias Fauste 5f391f7fff fix(settings): preserve content scroll position per page across navigation (#2277)
Settings nav items are plain Turbo Drive links (full-body visits); Turbo only
restores window scroll, so the nested overflow-y-auto content container snapped
to the top on every settings navigation.

Add a settings-scroll Stimulus controller that saves/restores the content scroll
keyed by pathname: returning to a page restores its scroll, a new page opens at
the top, and a same-page re-render (settings form auto-submit) keeps scroll.
Distinct from the nav's preserve-scroll controller, which keys by element id to
intentionally carry one position across pages.

Verified live: scroll 250 on Preferences -> Appearance opens at top -> back to
Preferences restores 250.
2026-06-11 15:52:07 +02:00

40 lines
1.3 KiB
JavaScript

import { Controller } from "@hotwired/stimulus";
// Preserves the settings content scroll position PER PAGE across Turbo Drive
// navigation. Settings nav items are plain links (full-body Turbo visits), and
// Turbo only restores window scroll — so this nested overflow-y-auto container
// snaps to top on every visit.
//
// Keyed by pathname: returning to a page restores its scroll, a brand-new page
// starts at the top, and a same-page re-render (e.g. a settings form that
// auto-submits) keeps scroll. This differs from the nav's `preserve-scroll`
// controller, which intentionally carries one position across all pages because
// the nav is the same persistent element on every settings page.
export default class extends Controller {
static positions = {};
connect() {
this.save = this.save.bind(this);
document.addEventListener("turbo:before-cache", this.save);
this.restore();
}
disconnect() {
document.removeEventListener("turbo:before-cache", this.save);
}
save() {
this.constructor.positions[window.location.pathname] = {
top: this.element.scrollTop,
left: this.element.scrollLeft,
};
}
restore() {
const pos = this.constructor.positions[window.location.pathname];
if (!pos) return;
this.element.scrollTop = pos.top;
this.element.scrollLeft = pos.left;
}
}