(frontend) fix tree keyboard toggle when children not yet loaded

node expansion now triggers lazy loading even without prior mouse interaction

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-09-16 13:13:33 +02:00
parent 69e7235f75
commit 1fb0897ebe
3 changed files with 61 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ and this project adheres to
- ♿(frontend) improve accessibility:
- #1354
- ✨fix tree keyboard toggle when children not yet loaded #1388
### Fixed

View File

@@ -20,6 +20,7 @@ import { useLeftPanelStore } from '@/features/left-panel';
import { useResponsiveStore } from '@/stores';
import { useKeyboardActivation } from '../hooks/useKeyboardActivation';
import { useLoadChildrenOnOpen } from '../hooks/useLoadChildrenOnOpen';
import SubPageIcon from './../assets/sub-page-logo.svg';
import { DocTreeItemActions } from './DocTreeItemActions';
@@ -96,6 +97,14 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
'.c__tree-view',
);
useLoadChildrenOnOpen(
node.data.value.id,
node.isOpen,
treeContext?.treeData.handleLoadChildren,
treeContext?.treeData.setChildren,
(doc.children?.length ?? 0) > 0 || doc.childrenCount === 0,
);
const docTitle = doc.title || untitledDocument;
const hasChildren = (doc.children?.length || 0) > 0;
const isExpanded = node.isOpen;

View File

@@ -0,0 +1,51 @@
import { useEffect, useRef } from 'react';
/**
* Lazily loads children for a tree node the first time it is expanded.
*/
export const useLoadChildrenOnOpen = <T>(
nodeId: string,
isOpen: boolean,
handleLoadChildren?: (id: string, signal: AbortSignal) => Promise<T[]>,
setChildren?: (id: string, children: T[]) => void,
isAlreadyLoaded: boolean = false,
) => {
const hasLoadedRef = useRef(false);
// Reset only if node changes AND it's not already loaded externally
useEffect(() => {
hasLoadedRef.current = isAlreadyLoaded;
}, [nodeId, isAlreadyLoaded]);
useEffect(() => {
if (!isOpen) {
return;
}
if (isAlreadyLoaded || hasLoadedRef.current) {
return;
}
if (!handleLoadChildren || !setChildren) {
return;
}
const abortCtrl = new AbortController();
hasLoadedRef.current = true; // prevent multiple fetches
void handleLoadChildren(nodeId, abortCtrl.signal)
.then((children) => {
if (!abortCtrl.signal.aborted) {
setChildren(nodeId, children);
}
})
.catch(() => {
// allow retry on next open
if (!abortCtrl.signal.aborted) {
hasLoadedRef.current = false;
}
});
return () => {
abortCtrl.abort();
};
}, [isOpen, nodeId, handleLoadChildren, setChildren, isAlreadyLoaded]);
};