Веб: вики — предки выбранной страницы раскрываются, когда её узел появился (#91)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a37ce0a59c
commit
01c28c890a
@ -59,4 +59,12 @@ describe("Tree", () => {
|
|||||||
expect(item("B")).toHaveAttribute("aria-expanded", "true");
|
expect(item("B")).toHaveAttribute("aria-expanded", "true");
|
||||||
expect(item("E")).toBeInTheDocument();
|
expect(item("E")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("выбранная страница появилась в nodes после монтирования — предки раскрываются", () => {
|
||||||
|
const { rerender } = render(<Tree nodes={[N("A", 0, null)]} selected="C" onOpen={() => {}} />);
|
||||||
|
expect(screen.queryByRole("treeitem", { name: "Стр C" })).toBeNull();
|
||||||
|
rerender(<Tree nodes={NODES} selected="C" onOpen={() => {}} />);
|
||||||
|
expect(item("B")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
expect(item("C")).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,17 +12,20 @@ export function Tree({ nodes, selected, onOpen }: { nodes: WikiNode[]; selected:
|
|||||||
const [focus, setFocus] = useState<string | null>(null);
|
const [focus, setFocus] = useState<string | null>(null);
|
||||||
const refs = useRef(new Map<string, HTMLDivElement>());
|
const refs = useRef(new Map<string, HTMLDivElement>());
|
||||||
|
|
||||||
// предки выбранной страницы раскрываются только при смене selected: новый массив nodes (перечитанное дерево) и ручное
|
// предки выбранной страницы раскрываются при смене selected и когда её узел впервые появился в nodes (дерево из кэша
|
||||||
// сворачивание родителя этого не повторяют; актуальные узлы читаются через ref и в зависимости не входят
|
// дополнилось свежим набором). Новый массив nodes, добавление других узлов и ручное сворачивание родителя этого не
|
||||||
|
// повторяют: selectedKnown меняется только с false на true. Актуальные узлы читаются через byCodeRef; его эффект
|
||||||
|
// объявлен ВЫШЕ эффекта раскрытия, поэтому в одном проходе ref уже свежий
|
||||||
|
const selectedKnown = !!selected && byCode.has(selected);
|
||||||
const byCodeRef = useRef(byCode);
|
const byCodeRef = useRef(byCode);
|
||||||
useEffect(() => { byCodeRef.current = byCode; });
|
useEffect(() => { byCodeRef.current = byCode; });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selected) return;
|
if (!selected || !selectedKnown) return;
|
||||||
const map = byCodeRef.current;
|
const map = byCodeRef.current;
|
||||||
const up: string[] = [];
|
const up: string[] = [];
|
||||||
for (let n = map.get(selected)?.parent_code; n && !up.includes(n); n = map.get(n)?.parent_code ?? null) up.push(n);
|
for (let n = map.get(selected)?.parent_code; n && !up.includes(n); n = map.get(n)?.parent_code ?? null) up.push(n);
|
||||||
if (up.length) setOpen((o) => (up.every((c) => o.has(c)) ? o : new Set([...o, ...up])));
|
if (up.length) setOpen((o) => (up.every((c) => o.has(c)) ? o : new Set([...o, ...up])));
|
||||||
}, [selected]);
|
}, [selected, selectedKnown]);
|
||||||
|
|
||||||
const visible = useMemo(() => nodes.filter((n) => {
|
const visible = useMemo(() => nodes.filter((n) => {
|
||||||
for (let p = n.parent_code, guard = 0; p && guard < nodes.length; p = byCode.get(p)?.parent_code ?? null, guard++) {
|
for (let p = n.parent_code, guard = 0; p && guard < nodes.length; p = byCode.get(p)?.parent_code ?? null, guard++) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user