Skip to content

Commit 59636c0

Browse files
authored
fix(sidebar): prevent docs page scrolling when typing in the sidebar filter (#34)
* fix: implement scroll locking during filter input and reset actions * fix(sidebar-filter): pin scrollY on input mutations to defeat Chrome scroll-into-view Replace the 15-frame rAF scroll-lock with a leaner combo: disable smooth scroll while the input has focus (so the unwanted scroll is a single instant jump, not a 250ms animation) and pin scrollY for ~200ms after each keystroke, extending the window on held-key repeats to cover Lit's batched re-renders. * fix(sidebar-filter): move scroll-behavior toggle into pinScrollY Previously the focus-time scroll-behavior toggle didn't cover the X clear button (focus leaves the input before resetFilter runs, restoring smooth mid-mutation and causing a visible stutter). Move the toggle into pinScrollY itself so 'auto' is in effect for exactly the pin window, regardless of focus state. Prior inline value (DocsLayout's 'smooth') is restored when the window ends.
1 parent 00df9c7 commit 59636c0

1 file changed

Lines changed: 31 additions & 3 deletions

File tree

src/components/DocsSidebar/sidebar-filter.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,9 @@ class SidebarFilter extends HTMLElement {
150150
// ── Events ───────────────────────────────────────────────────────────────
151151

152152
private bindEvents(): void {
153-
// igc-input fires 'igcInput' instead of the native 'input' event.
154-
this.input.addEventListener('igcInput', this.onFilterInput as EventListener);
153+
this.input.addEventListener('igcInput', this.onFilterInput as EventListener);
155154
this.input.addEventListener('keydown', this.onFilterKeydown);
156155
this.clearBtn.addEventListener('click', this.onClearClick);
157-
158156
this.bindTreeEvents();
159157
}
160158

@@ -167,14 +165,43 @@ class SidebarFilter extends HTMLElement {
167165
}
168166

169167
private onFilterInput = (): void => {
168+
this.pinScrollY();
170169
safeSet(FILTER_KEY, this.input.value);
171170
this.applyFilter(this.input.value, 'user');
172171
};
173172

174173
private onFilterKeydown = (e: KeyboardEvent): void => {
174+
this.pinScrollY();
175175
if (e.key === 'Escape' && this.input.value) this.resetFilter();
176176
};
177177

178+
/**
179+
* Chrome-only: filter mutations trigger a browser-internal scroll. Pin
180+
* scrollY via rAF for ~200ms (before-paint, invisible) and force
181+
* `scroll-behavior: auto` so it's an instant jump, not a smooth animation
182+
* we'd have to fight each frame. Prior inline value is restored at end.
183+
*/
184+
private pinFrames = 0;
185+
private prevScrollBehavior: string | null = null;
186+
private pinScrollY = (): void => {
187+
const wasActive = this.pinFrames > 0;
188+
const y = window.scrollY;
189+
this.pinFrames = 10;
190+
if (wasActive) return;
191+
this.prevScrollBehavior = document.documentElement.style.getPropertyValue('scroll-behavior');
192+
document.documentElement.style.setProperty('scroll-behavior', 'auto', 'important');
193+
const tick = (): void => {
194+
if (window.scrollY !== y) window.scrollTo({ top: y, behavior: 'instant' as ScrollBehavior });
195+
if (--this.pinFrames > 0) requestAnimationFrame(tick);
196+
else {
197+
if (this.prevScrollBehavior) document.documentElement.style.setProperty('scroll-behavior', this.prevScrollBehavior);
198+
else document.documentElement.style.removeProperty('scroll-behavior');
199+
this.prevScrollBehavior = null;
200+
}
201+
};
202+
tick();
203+
};
204+
178205
private onClearClick = (): void => {
179206
this.resetFilter();
180207
this.input.focus();
@@ -395,6 +422,7 @@ class SidebarFilter extends HTMLElement {
395422
}
396423

397424
private resetFilter(): void {
425+
this.pinScrollY();
398426
this.input.value = '';
399427
this.syncClearButton('');
400428
safeRemove(FILTER_KEY);

0 commit comments

Comments
 (0)