TheBird

hooks.ts

2.4 kB · typescript · 109 lines

1import { useCallback, useSyncExternalStore } from "react";2import { get as getMode, none, subscribe as onMode } from "../client/mode.ts";3import type { Primary } from "./shop.ts";45/* NOW */67const ticks = new Set<() => void>();89let now = 0;1011let timer: ReturnType<typeof setInterval> | null = null;1213function beat() {14  now = Date.now();15  for (const fn of ticks) fn();16}1718function run() {19  if (timer) clearInterval(timer);20  timer = null;21  if (document.hidden || !ticks.size) return;22  beat();23  timer = setInterval(beat, 1000);24}2526function onTick(fn: () => void) {27  ticks.add(fn);28  if (ticks.size === 1) {29    document.addEventListener("visibilitychange", run);30    run();31  }32  return () => {33    ticks.delete(fn);34    if (ticks.size) return;35    document.removeEventListener("visibilitychange", run);36    run();37  };38}3940const nowSnapshot = () => now;4142const nowServer = () => 0;4344export function useNow(initial: number) {45  return useSyncExternalStore(onTick, nowSnapshot, nowServer) || initial;46}4748/* MEDIA */4950export function useMediaQuery(query: string) {51  const subscribe = useCallback(52    (fn: () => void) => {53      const media = matchMedia(query);54      media.addEventListener("change", fn);55      return () => media.removeEventListener("change", fn);56    },57    [query],58  );59  return useSyncExternalStore(60    subscribe,61    useCallback(() => matchMedia(query).matches, [query]),62    () => false,63  );64}6566/* SEARCH */6768const NAV = "cm:nav";6970export const bump = () => dispatchEvent(new Event(NAV));7172function onNav(fn: () => void) {73  addEventListener("popstate", fn);74  addEventListener(NAV, fn);75  return () => {76    removeEventListener("popstate", fn);77    removeEventListener(NAV, fn);78  };79}8081const searchSnapshot = () => location.search;8283const searchServer = () => "";8485export const useSearch = () => useSyncExternalStore(onNav, searchSnapshot, searchServer);8687/* PRIMARY */8889const pickers = new Set<() => void>();9091let picked: Primary | null = null;9293export function pickPrimary(next: Primary) {94  picked = next;95  for (const fn of pickers) fn();96}9798function onPick(fn: () => void) {99  pickers.add(fn);100  return () => void pickers.delete(fn);101}102103const pickedSnapshot = () => picked;104105export function usePrimary(initial: Primary): Primary {106  const chosen = useSyncExternalStore(onPick, pickedSnapshot, none);107  const mode = useSyncExternalStore(onMode, getMode, none);108  return chosen ?? mode ?? initial;109}