Skip to content

refactor(hooks): replace lock_scroll.js with Rust web_sys implementation#21

Open
mp-c0de wants to merge 2 commits intorust-ui:mainfrom
mp-c0de:refactor/replace-lock-scroll-js-with-rust
Open

refactor(hooks): replace lock_scroll.js with Rust web_sys implementation#21
mp-c0de wants to merge 2 commits intorust-ui:mainfrom
mp-c0de:refactor/replace-lock-scroll-js-with-rust

Conversation

@mp-c0de
Copy link
Copy Markdown
Contributor

@mp-c0de mp-c0de commented Apr 10, 2026

Summary

Resolves #1.

Replaces public/hooks/lock_scroll.js (254 lines of vanilla JS) with a pure Rust implementation using web_sys. The new module lives in app_crates/registry/src/hooks/scroll_lock.rs and exposes the same API:

  • lock() — locks scroll on body + all scrollable containers, compensates scrollbar width
  • unlock(delay_ms) — restores scroll after optional delay (for exit animations)
  • is_locked() — returns current lock state

The Rust implementation:

  • Batches DOM reads before writes to minimise reflows (matching the JS behaviour)
  • Registers window.ScrollLock via wasm_bindgen closures so existing inline scripts work unchanged
  • Uses thread_local! { RefCell } for state (wasm is single-threaded)
  • Preserves the same exclusion list (ScrollArea, CommandList, SelectContent, etc.)
  • Handles scrollbar width compensation on fixed-position elements

Changes

  • New: app_crates/registry/src/hooks/scroll_lock.rs — full Rust implementation
  • Modified: Cargo.toml — added CssStyleDeclaration to web-sys features
  • Modified: app/src/app.rs — calls scroll_lock::init() at app startup (wasm-only via #[cfg])
  • Modified: app/src/shell.rs — removed global <script async src="/hooks/lock_scroll.js">
  • Modified: 8 component files — removed <script src="/hooks/lock_scroll.js"> from Sheet, Dialog, Select, DropdownMenu, MultiSelect, Command, Menubar, ContextMenu
  • Deleted: public/hooks/lock_scroll.js

Not included (follow-up work)

  • Regenerating public/registry/ files (tree.md, component markdown)
  • Updating crates/ui-cli/ test fixtures that reference lock_scroll.js
  • Adding the missing Playwright test cases (scrollbar compensation, unlock delay, nested containers)

Test plan

  • Verified cargo check compiles with zero errors on scroll_lock.rs
  • Ran cargo leptos watch — server starts without panics
  • Tested Dialog component — scroll locks on open, restores on close
  • Tested Sheet component — scroll locks on open, restores after animation delay
  • Verified window.ScrollLock is registered and isLocked() returns correct state
  • Run existing Playwright e2e tests for all 8 affected components
  • Run use-lock-body-scroll.spec.ts hook test

Resolves rust-ui#1. Rewrites the scroll lock utility in pure Rust using web_sys,
eliminating the vanilla JS dependency.

- Add scroll_lock.rs with lock(), unlock(delay), is_locked() API
- Register window.ScrollLock via wasm_bindgen closures at app init
- Add CssStyleDeclaration to web-sys features for getComputedStyle
- Remove <script src="/hooks/lock_scroll.js"> from shell and 8 components
- Delete public/hooks/lock_scroll.js (254 lines of JS)
@mp-c0de
Copy link
Copy Markdown
Contributor Author

mp-c0de commented Apr 11, 2026

Also fixes #23 (Safari tab buttons)

While testing this PR I discovered it incidentally fixes #23 as well. The inline <script src="/hooks/lock_scroll.js"></script> tags this PR removes were causing a hydration crash in Safari (tachys finds the <script> where it expects a Leptos marker comment), which silently broke all reactive interactivity on docs pages.

Verified by A/B testing — clean main reproduces the Safari cold-start hydration error every time, applying this PR fixes it on first load every time.

Full root cause analysis in #23.

The init() call is already wrapped with #[cfg(target_arch = "wasm32")],
but the use statement was unconditional, causing an unused-import warning
on the SSR build.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace lock_scroll.js with a Rust implementation using web_sys

1 participant