pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor scrolling with a reusable component

Pas 544fe97c 1199a21d

+102 -38
+2 -1
src/components/player/atoms/Episodes.tsx
··· 20 20 import { usePlayerStore } from "@/stores/player/store"; 21 21 import { usePreferencesStore } from "@/stores/preferences"; 22 22 import { useProgressStore } from "@/stores/progress"; 23 + import { scrollToElement } from "@/utils/scroll"; 23 24 24 25 import { hasAired } from "../utils/aired"; 25 26 ··· 832 833 carouselRef.current.scrollLeft += scrollPosition; 833 834 } else { 834 835 // vertical scroll 835 - activeEpisodeRef.current.scrollIntoView({ 836 + scrollToElement(activeEpisodeRef.current, { 836 837 behavior: "smooth", 837 838 block: "center", 838 839 });
+13 -37
src/pages/Settings.tsx
··· 37 37 import { usePreferencesStore } from "@/stores/preferences"; 38 38 import { useSubtitleStore } from "@/stores/subtitles"; 39 39 import { usePreviewThemeStore, useThemeStore } from "@/stores/theme"; 40 + import { scrollToElement, scrollToHash } from "@/utils/scroll"; 40 41 41 42 import { SubPageLayout } from "./layouts/SubPageLayout"; 42 43 import { AppInfoPart } from "./parts/settings/AppInfoPart"; ··· 195 196 const categoryId = subSectionToCategory[hashId]; 196 197 setSelectedCategory(categoryId); 197 198 // Wait for the section to render, then scroll 198 - setTimeout(() => { 199 - const element = document.querySelector(hash); 200 - if (element) { 201 - element.scrollIntoView({ behavior: "smooth" }); 202 - } 203 - }, 100); 199 + scrollToHash(hash, { delay: 100 }); 204 200 } else if (validCategories.includes(hashId)) { 205 201 // It's a category hash 206 202 setSelectedCategory(hashId); 207 - const element = document.querySelector(hash); 208 - if (element) { 209 - element.scrollIntoView({ behavior: "smooth" }); 210 - } 203 + scrollToHash(hash); 211 204 } else { 212 205 // Try to find the element anyway (might be a sub-section) 213 206 const element = document.querySelector(hash); ··· 218 211 const categoryId = parentSection.id; 219 212 if (validCategories.includes(categoryId)) { 220 213 setSelectedCategory(categoryId); 221 - setTimeout(() => { 222 - element.scrollIntoView({ behavior: "smooth" }); 223 - }, 100); 214 + scrollToHash(hash, { delay: 100 }); 224 215 } 225 216 } else { 226 - element.scrollIntoView({ behavior: "smooth" }); 217 + scrollToHash(hash); 227 218 } 228 219 } 229 220 } ··· 251 242 if (subSectionToCategory[hashId]) { 252 243 const categoryId = subSectionToCategory[hashId]; 253 244 setSelectedCategory(categoryId); 254 - setTimeout(() => { 255 - const element = document.querySelector(hash); 256 - if (element) { 257 - element.scrollIntoView({ behavior: "smooth" }); 258 - } 259 - }, 100); 245 + scrollToHash(hash, { delay: 100 }); 260 246 } else if (validCategories.includes(hashId)) { 261 247 setSelectedCategory(hashId); 262 - setTimeout(() => { 263 - const element = document.querySelector(hash); 264 - if (element) { 265 - element.scrollIntoView({ behavior: "smooth" }); 266 - } 267 - }, 100); 248 + scrollToHash(hash, { delay: 100 }); 268 249 } else { 269 250 const element = document.querySelector(hash); 270 251 if (element) { ··· 273 254 const categoryId = parentSection.id; 274 255 if (validCategories.includes(categoryId)) { 275 256 setSelectedCategory(categoryId); 276 - setTimeout(() => { 277 - element.scrollIntoView({ behavior: "smooth" }); 278 - }, 100); 257 + scrollToHash(hash, { delay: 100 }); 279 258 } 280 259 } else { 281 - element.scrollIntoView({ behavior: "smooth" }); 260 + scrollToHash(hash); 282 261 } 283 262 } 284 263 } ··· 372 351 } 373 352 374 353 // Scroll to first highlighted element 375 - const firstHighlighted = document.querySelector(".search-highlight"); 376 - if (firstHighlighted) { 377 - firstHighlighted.scrollIntoView({ 378 - behavior: "smooth", 379 - block: "center", 380 - }); 381 - } 354 + scrollToElement(".search-highlight", { 355 + behavior: "smooth", 356 + block: "center", 357 + }); 382 358 } 383 359 }, []); 384 360
+87
src/utils/scroll.ts
··· 1 + /** 2 + * Scrolls an element into view with configurable options 3 + * @param selector - CSS selector string, Element, or null 4 + * @param options - Scroll options 5 + * @returns void (always returns, even if element not found) 6 + */ 7 + export function scrollToElement( 8 + selector: string | Element | null, 9 + options?: { 10 + behavior?: ScrollBehavior; 11 + block?: ScrollLogicalPosition; 12 + inline?: ScrollLogicalPosition; 13 + offset?: number; // Additional offset in pixels (positive = scroll down more) 14 + delay?: number; // Delay in milliseconds before scrolling (useful when element needs to render) 15 + }, 16 + ): void { 17 + const { 18 + behavior = "smooth", 19 + block = "start", 20 + inline = "nearest", 21 + offset = 0, 22 + delay = 0, 23 + } = options || {}; 24 + 25 + const scroll = (): void => { 26 + let element: Element | null = null; 27 + 28 + if (selector === null) { 29 + return; 30 + } 31 + 32 + if (typeof selector === "string") { 33 + element = document.querySelector(selector); 34 + } else { 35 + element = selector; 36 + } 37 + 38 + if (!element) { 39 + return; 40 + } 41 + 42 + if (offset === 0) { 43 + // Use native scrollIntoView when no offset is needed 44 + element.scrollIntoView({ behavior, block, inline }); 45 + return; 46 + } 47 + 48 + // Custom scroll with offset 49 + const elementRect = element.getBoundingClientRect(); 50 + const absoluteElementTop = elementRect.top + window.pageYOffset; 51 + const offsetPosition = absoluteElementTop - offset; 52 + 53 + window.scrollTo({ 54 + top: offsetPosition, 55 + behavior, 56 + }); 57 + }; 58 + 59 + if (delay > 0) { 60 + setTimeout(() => { 61 + scroll(); 62 + }, delay); 63 + return; 64 + } 65 + 66 + scroll(); 67 + } 68 + 69 + /** 70 + * Scrolls to an element by hash (useful for hash navigation) 71 + * @param hash - Hash string (with or without #) 72 + * @param options - Scroll options 73 + * @returns void (always returns, even if element not found) 74 + */ 75 + export function scrollToHash( 76 + hash: string, 77 + options?: { 78 + behavior?: ScrollBehavior; 79 + block?: ScrollLogicalPosition; 80 + inline?: ScrollLogicalPosition; 81 + offset?: number; 82 + delay?: number; 83 + }, 84 + ): void { 85 + const normalizedHash = hash.startsWith("#") ? hash : `#${hash}`; 86 + scrollToElement(normalizedHash, options); 87 + }