🐛 Open a never scrolled token set at the top of the list (#12119)

The tokens list container stays mounted when another token set is
selected; only the restore key changes. With no saved position for the
new set, use-restore-scroll did nothing, so the list kept the scroll
position of the previous set. Scroll to the top when nothing is saved
for the key. Freshly mounted panels are already at the top, so Layers
and Assets do not change.

Closes #11973

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-10-06 15:40:24 +02:00 committed by GitHub
parent ff74c38bbf
commit 5981d1ee66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 31 additions and 3 deletions

View File

@ -45,17 +45,26 @@
(when (needs-restore-retry? prev-height height attempts)
(vreset! raf* (tm/raf #(restore-loop! node saved height (inc attempts) raf*))))))
(defn- restore-position!
"Restores saved, or scrolls to the top when there is none: the node can
stay mounted while the key changes (token sets), so it may still hold
another list's position."
[node saved raf*]
(if (some? saved)
(restore-loop! node saved -1 0 raf*)
(restore-scroll! node 0)))
(defn use-restore-scroll
"Restores the scrollTop saved under [panel id] on mount and whenever
panel or id change. Retries while the content height keeps changing so
deep positions in lazily rendered lists are not clamped to the first
chunk. Missing key or node is a silent no-op."
chunk. With no saved position the node goes to the top. Missing node is
a silent no-op."
[store* panel id node-ref]
(mf/with-effect [panel id]
(let [raf* (volatile! nil)]
(when-let [node (mf/ref-val node-ref)]
(when-let [saved (get @store* [panel id])]
(restore-loop! node saved -1 0 raf*)))
(restore-position! node (get @store* [panel id]) raf*))
(fn []
(when-some [raf @raf*]
(tm/cancel-af! raf))))))

View File

@ -11,6 +11,7 @@
[cljs.test :as t :include-macros true]))
(def ^:private restore-loop! @#'sc/restore-loop!)
(def ^:private restore-position! @#'sc/restore-position!)
(defn- scroll-event
[scroll-top]
@ -64,3 +65,21 @@
((first @scheduled))
(t/is (= 250 (.-scrollTop node)))
(t/is (= 1 (count @scheduled)) "no further frames once settled"))))
(t/deftest restore-position-without-saved-value-goes-to-top
(let [node #js {:scrollTop 400 :scrollHeight 900}
scheduled (atom [])
raf* (volatile! nil)]
(with-redefs [tm/raf (fn [f] (swap! scheduled conj f) 1)]
(restore-position! node nil raf*)
(t/is (= 0 (.-scrollTop node)) "a node kept mounted drops the old position")
(t/is (empty? @scheduled) "no retry loop for the top"))))
(t/deftest restore-position-with-saved-value-runs-loop
(let [node #js {:scrollTop 0 :scrollHeight 900}
scheduled (atom [])
raf* (volatile! nil)]
(with-redefs [tm/raf (fn [f] (swap! scheduled conj f) 1)]
(restore-position! node 250 raf*)
(t/is (= 250 (.-scrollTop node)))
(t/is (= 1 (count @scheduled)) "first pass schedules a settle check"))))