72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
"use client";
|
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
import { usePathname } from "next/navigation";
|
|
|
|
type Ctx = {
|
|
ids: Set<number>;
|
|
has: (id: number) => boolean;
|
|
toggle: (id: number) => void;
|
|
selectMany: (ids: number[]) => void;
|
|
clear: () => void;
|
|
visibleIds: number[];
|
|
setVisibleIds: (ids: number[]) => void;
|
|
};
|
|
|
|
const SelectCtx = createContext<Ctx | null>(null);
|
|
|
|
export function SelectionProvider({ children }: { children: React.ReactNode }) {
|
|
const [ids, setIds] = useState<Set<number>>(new Set());
|
|
const [visibleIds, setVisibleIdsState] = useState<number[]>([]);
|
|
// Guard against fresh-array identity churn: server-side renders pass a new
|
|
// `number[]` reference every time, which would otherwise re-fire all consumers.
|
|
const setVisibleIds = useCallback((next: number[]) => {
|
|
setVisibleIdsState((cur) => {
|
|
if (cur.length === next.length && cur.every((v, i) => v === next[i])) return cur;
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const toggle = useCallback((id: number) => setIds((cur) => {
|
|
const next = new Set(cur);
|
|
if (next.has(id)) next.delete(id); else next.add(id);
|
|
return next;
|
|
}), []);
|
|
const selectMany = useCallback((newIds: number[]) => setIds((cur) => {
|
|
const next = new Set(cur);
|
|
newIds.forEach((i) => next.add(i));
|
|
return next;
|
|
}), []);
|
|
const clear = useCallback(() => setIds(new Set()), []);
|
|
|
|
// Global route-change cleanup: pages without RegisterVisible (e.g.
|
|
// /actress, /category, /tag, /search) would otherwise carry stale
|
|
// selections across navigation. Clear on any pathname change.
|
|
const pathname = usePathname();
|
|
const lastPath = useRef<string | null>(null);
|
|
useEffect(() => {
|
|
if (lastPath.current !== null && lastPath.current !== pathname) {
|
|
setIds(new Set());
|
|
setVisibleIdsState([]);
|
|
}
|
|
lastPath.current = pathname;
|
|
}, [pathname]);
|
|
|
|
const value = useMemo<Ctx>(() => ({
|
|
ids,
|
|
has: (id) => ids.has(id),
|
|
toggle,
|
|
selectMany,
|
|
clear,
|
|
visibleIds,
|
|
setVisibleIds,
|
|
}), [ids, toggle, selectMany, clear, visibleIds, setVisibleIds]);
|
|
|
|
return <SelectCtx.Provider value={value}>{children}</SelectCtx.Provider>;
|
|
}
|
|
|
|
export function useSelection() {
|
|
const ctx = useContext(SelectCtx);
|
|
if (!ctx) throw new Error("useSelection must be used within SelectionProvider");
|
|
return ctx;
|
|
}
|