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 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(() => {
if (initialFocusedBlockId) {
setHighlightedBlockId(initialFocusedBlockId);
const timer = setTimeout(() => {
focusBlockInput(initialFocusedBlockId, 'end');
const el = textareaMapRef.current.get(initialFocusedBlockId);
const focusBlockInput = useCallback(
(blockId: string, cursor: 'start' | 'end' | 'select' = 'end') => {
setFocusedBlockId(blockId);
const el = textareaMapRef.current.get(blockId);
if (el) {
requestAnimationFrame(() => {
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' });
});
} else {
pendingFocusRef.current = { blockId, cursor };
}
}, 80);
const fadeTimer = setTimeout(() => {
setHighlightedBlockId(null);
}, 2500);
return () => {
clearTimeout(timer);
clearTimeout(fadeTimer);
};
}
}, [initialFocusedBlockId]);
},
[]
);
const registerTextareaRef = useCallback((blockId: string, el: HTMLTextAreaElement | null) => {
if (el) {
@@ -74,13 +80,19 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
const target = pendingFocusRef.current;
pendingFocusRef.current = null;
requestAnimationFrame(() => {
setFocusedBlockId(blockId);
el.focus();
const pos = target.cursor === 'end' ? el.value.length : 0;
try {
if (target.cursor === 'select') {
el.select();
} else {
const pos = target.cursor === 'end' ? el.value.length : 0;
el.setSelectionRange(pos, pos);
}
} catch {
// ignore
}
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
}
} else {
@@ -88,23 +100,26 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
}
}, []);
const focusBlockInput = useCallback((blockId: string, cursor: 'start' | 'end' = 'end') => {
setFocusedBlockId(blockId);
const el = textareaMapRef.current.get(blockId);
if (el) {
requestAnimationFrame(() => {
el.focus();
const pos = cursor === 'end' ? el.value.length : 0;
try {
el.setSelectionRange(pos, pos);
} catch {
// ignore
useEffect(() => {
if (initialFocusedBlockId) {
setHighlightedBlockId(initialFocusedBlockId);
focusBlockInput(initialFocusedBlockId, 'select');
const timer = setTimeout(() => {
focusBlockInput(initialFocusedBlockId, 'select');
}, 70);
const timer2 = setTimeout(() => {
focusBlockInput(initialFocusedBlockId, 'select');
}, 200);
const fadeTimer = setTimeout(() => {
setHighlightedBlockId(null);
}, 2800);
return () => {
clearTimeout(timer);
clearTimeout(timer2);
clearTimeout(fadeTimer);
};
}
});
} else {
pendingFocusRef.current = { blockId, cursor };
}
}, []);
}, [initialFocusedBlockId, blocks.length, focusBlockInput]);
// Compute visibility based on collapsed ancestor blocks
const visibleEntries = useMemo(() => {