Files
yaak-mountain-loop/src-web/components/core/tree/atoms.ts
Gregory Schier b4a1c418bb Run oxfmt across repo, add format script and docs
Add .oxfmtignore to skip generated bindings and wasm-pack output.
Add npm format script, update DEVELOPMENT.md for Vite+ toolchain,
and format all non-generated files with oxfmt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:15:49 -07:00

103 lines
3.5 KiB
TypeScript

import { atom } from "jotai";
import { atomFamily, selectAtom } from "jotai/utils";
import { atomWithKVStorage } from "../../../lib/atoms/atomWithKVStorage";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const selectedIdsFamily = atomFamily((_treeId: string) => {
return atom<string[]>([]);
});
export const isSelectedFamily = atomFamily(
({ treeId, itemId }: { treeId: string; itemId: string }) => {
return selectAtom(selectedIdsFamily(treeId), (ids) => ids.includes(itemId), Object.is);
},
(a, b) => a.treeId === b.treeId && a.itemId === b.itemId,
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const focusIdsFamily = atomFamily((_treeId: string) => {
return atom<{ lastId: string | null; anchorId: string | null }>({ lastId: null, anchorId: null });
});
export const isLastFocusedFamily = atomFamily(
({ treeId, itemId }: { treeId: string; itemId: string }) =>
selectAtom(focusIdsFamily(treeId), (v) => v.lastId === itemId, Object.is),
(a, b) => a.treeId === b.treeId && a.itemId === b.itemId,
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const draggingIdsFamily = atomFamily((_treeId: string) => {
return atom<string[]>([]);
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const hoveredParentFamily = atomFamily((_treeId: string) => {
return atom<{
index: number | null;
childIndex: number | null;
parentId: string | null;
parentDepth: number | null;
}>({
index: null,
childIndex: null,
parentId: null,
parentDepth: null,
});
});
export const isParentHoveredFamily = atomFamily(
({ treeId, parentId }: { treeId: string; parentId: string | null }) =>
selectAtom(hoveredParentFamily(treeId), (v) => v.parentId === parentId, Object.is),
(a, b) => a.treeId === b.treeId && a.parentId === b.parentId,
);
export const isAncestorHoveredFamily = atomFamily(
({ treeId, ancestorIds }: { treeId: string; ancestorIds: string[] }) =>
selectAtom(
hoveredParentFamily(treeId),
(v) => v.parentId && ancestorIds.includes(v.parentId),
Object.is,
),
(a, b) => a.treeId === b.treeId && a.ancestorIds.join(",") === b.ancestorIds.join(","),
);
export const isIndexHoveredFamily = atomFamily(
({ treeId, index }: { treeId: string; index: number }) =>
selectAtom(hoveredParentFamily(treeId), (v) => v.index === index, Object.is),
(a, b) => a.treeId === b.treeId && a.index === b.index,
);
export const hoveredParentDepthFamily = atomFamily((treeId: string) =>
selectAtom(
hoveredParentFamily(treeId),
(s) => s.parentDepth,
(a, b) => Object.is(a, b), // prevents re-render unless the value changes
),
);
export const collapsedFamily = atomFamily((workspaceId: string) => {
const key = ["sidebar_collapsed", workspaceId ?? "n/a"];
return atomWithKVStorage<Record<string, boolean>>(key, {});
});
export const isCollapsedFamily = atomFamily(
({ treeId, itemId = "n/a" }: { treeId: string; itemId: string | undefined }) =>
atom(
// --- getter ---
(get) => !!get(collapsedFamily(treeId))[itemId],
// --- setter ---
(get, set, next: boolean | ((prev: boolean) => boolean)) => {
const a = collapsedFamily(treeId);
const prevMap = get(a);
const prevValue = !!prevMap[itemId];
const value = typeof next === "function" ? next(prevValue) : next;
if (value === prevValue) return; // no-op
set(a, { ...prevMap, [itemId]: value });
},
),
(a, b) => a.treeId === b.treeId && a.itemId === b.itemId,
);