40 lines (35 loc) · 1.32 KB
 1// Tiny shared helpers used across modules.
 2
 3/** Query one element. Pass the element type when you need more than `Element`. */
 4export const $ = <T extends Element = Element>(
 5  selector: string,
 6  root: ParentNode = document,
 7): T | null => root.querySelector<T>(selector);
 8
 9/** Query all elements as an array. */
10export const $$ = <T extends Element = Element>(
11  selector: string,
12  root: ParentNode = document,
13): T[] => [...root.querySelectorAll<T>(selector)];
14
15/**
16 * Event targets arrive typed as `EventTarget | null`, which has no DOM API.
17 * Narrow once here instead of casting at every listener.
18 */
19export const asElement = (target: EventTarget | null): Element | null =>
20  target instanceof Element ? target : null;
21
22/**
23 * Storage that no-ops when unavailable (private mode, blocked storage).
24 * Resolves once at load; callers use optional chaining: `storage?.getItem(...)`.
25 */
26const safeStorage = (backing: Storage): Storage | null => {
27  try {
28    const key = "__storage-test";
29    backing.setItem(key, "1");
30    backing.removeItem(key);
31    return backing;
32  } catch (_) {
33    return null;
34  }
35};
36export const storage = safeStorage(localStorage);
37export const sessionStore = safeStorage(sessionStorage);
38
39const _rmq = window.matchMedia("(prefers-reduced-motion: reduce)");
40export const prefersReducedMotion = (): boolean => _rmq.matches;