Dev #7

Merged
sandrodach merged 33 commits from dev into main 2026-10-27 11:42:12 +01:00
Showing only changes of commit 149cfdc244 - Show all commits
@@ -45,27 +45,33 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null); const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
const textareaMapRef = useRef<Map<string, HTMLTextAreaElement>>(new Map()); const textareaMapRef = useRef<Map<string, HTMLTextAreaElement>>(new Map());
const pendingFocusRef = useRef<{ blockId: string; cursor: 'start' | 'end' } | null>(null); const pendingFocusRef = useRef<{ blockId: string; cursor: 'start' | 'end' | 'select' } | null>(null);
useEffect(() => { const focusBlockInput = useCallback(
if (initialFocusedBlockId) { (blockId: string, cursor: 'start' | 'end' | 'select' = 'end') => {
setHighlightedBlockId(initialFocusedBlockId); setFocusedBlockId(blockId);
const timer = setTimeout(() => { const el = textareaMapRef.current.get(blockId);
focusBlockInput(initialFocusedBlockId, 'end'); if (el) {
const el = textareaMapRef.current.get(initialFocusedBlockId); requestAnimationFrame(() => {
if (el) { el.focus();
try {
if (cursor === 'select') {
el.select();
} else {
const pos = cursor === 'end' ? el.value.length : 0;
el.setSelectionRange(pos, pos);
}
} catch {
// ignore
}
el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} });
}, 80); } else {
const fadeTimer = setTimeout(() => { pendingFocusRef.current = { blockId, cursor };
setHighlightedBlockId(null); }
}, 2500); },
return () => { []
clearTimeout(timer); );
clearTimeout(fadeTimer);
};
}
}, [initialFocusedBlockId]);
const registerTextareaRef = useCallback((blockId: string, el: HTMLTextAreaElement | null) => { const registerTextareaRef = useCallback((blockId: string, el: HTMLTextAreaElement | null) => {
if (el) { if (el) {
@@ -74,13 +80,19 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
const target = pendingFocusRef.current; const target = pendingFocusRef.current;
pendingFocusRef.current = null; pendingFocusRef.current = null;
requestAnimationFrame(() => { requestAnimationFrame(() => {
setFocusedBlockId(blockId);
el.focus(); el.focus();
const pos = target.cursor === 'end' ? el.value.length : 0;
try { try {
el.setSelectionRange(pos, pos); if (target.cursor === 'select') {
el.select();
} else {
const pos = target.cursor === 'end' ? el.value.length : 0;
el.setSelectionRange(pos, pos);
}
} catch { } catch {
// ignore // ignore
} }
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}); });
} }
} else { } else {
@@ -88,23 +100,26 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
} }
}, []); }, []);
const focusBlockInput = useCallback((blockId: string, cursor: 'start' | 'end' = 'end') => { useEffect(() => {
setFocusedBlockId(blockId); if (initialFocusedBlockId) {
const el = textareaMapRef.current.get(blockId); setHighlightedBlockId(initialFocusedBlockId);
if (el) { focusBlockInput(initialFocusedBlockId, 'select');
requestAnimationFrame(() => { const timer = setTimeout(() => {
el.focus(); focusBlockInput(initialFocusedBlockId, 'select');
const pos = cursor === 'end' ? el.value.length : 0; }, 70);
try { const timer2 = setTimeout(() => {
el.setSelectionRange(pos, pos); focusBlockInput(initialFocusedBlockId, 'select');
} catch { }, 200);
// ignore const fadeTimer = setTimeout(() => {
} setHighlightedBlockId(null);
}); }, 2800);
} else { return () => {
pendingFocusRef.current = { blockId, cursor }; clearTimeout(timer);
clearTimeout(timer2);
clearTimeout(fadeTimer);
};
} }
}, []); }, [initialFocusedBlockId, blocks.length, focusBlockInput]);
// Compute visibility based on collapsed ancestor blocks // Compute visibility based on collapsed ancestor blocks
const visibleEntries = useMemo(() => { const visibleEntries = useMemo(() => {