fix(scroll): scope StyleCache.reset to the locked element#641
Merged
Conversation
size-limit report 📦
|
deb3e42 to
f0b1035
Compare
StyleCache.reset() restored and cleared the entire cache, so unlocking one scroll lock wiped the saved styles of another still-active lock sharing the module-level cache (e.g. an Overlay locking one container while a ContextMenu locks another). Make it a per-element lock registry: reset(el) is required and restores only that element, and set() is a no-op when the element is already cached. Collapse lockStandardScroll to a single set() call so its style keys are captured together. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01We9NqptZjfbvXRL4hE1xri
f0b1035 to
b74bcef
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
StyleCache.reset()restored and cleared the entire cache, regardless of which element a given lock owned:lockStandardScrollandlockSafariScrolleach use a module-levelStyleCacheshared across all their calls. So when two different elements are locked at once — e.g. anOverlaylocking one container (useScrollLock({ containerRef })) while aContextMenulocks another (useScrollLock({ originRef })) — restoring one lock wipes the saved originals of the other still-active lock. The survivor can no longer restore its styles (scroll lock leaks or releases early).Note this is the cross-container case. The per-container guard in
lockScroll(if (container.style.overflow === "hidden") return) only blocks re-locking the same element, so two different elements both land in the shared cache.Fix
Make
StyleCachea per-element lock registry:reset(el)is now required and scoped — it restores and removes only that element's entry, never touching other active locks.set(el, styles)is a no-op when the element is already cached — locking an already-locked element keeps the originals captured the first time, instead of merging.lockStandardScrollbuilds its styles into one object and callsset()once (overflow + optionalscrollbar-gutter/paddingRighttogether), so all keys are captured atomically — required now thatset()is idempotent per element.lockSafariScrollalready does a singleset(); its unlock now callsreset(document.body).API note
reset()now requires an element (the global no-arg form is removed). The only callers are the two lock modules; both pass the element they locked.Verification
tsc --noEmit→ 0 errors.oxlint→ clean.StyleCache.test.tsupdated for the new semantics (scoped reset, idempotent set, no-op reset for uncached element).scroll/tests/lock.test.ts: unlocking one container does not affect another locked container — confirmed it fails against the old global-wipereset()and passes with the scoped fix.How to test
Found during a full package bug review. Part of the scroll-lock cluster.
Generated by Claude Code