diff --git a/frontend/src/features/workspace/OutlinerTree.tsx b/frontend/src/features/workspace/OutlinerTree.tsx index 107e8d5..41b1762 100644 --- a/frontend/src/features/workspace/OutlinerTree.tsx +++ b/frontend/src/features/workspace/OutlinerTree.tsx @@ -45,27 +45,33 @@ export const OutlinerTree: React.FC = ({ const [dragOverIndex, setDragOverIndex] = useState(null); const textareaMapRef = useRef>(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); - if (el) { + 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' }); - } - }, 80); - const fadeTimer = setTimeout(() => { - setHighlightedBlockId(null); - }, 2500); - return () => { - clearTimeout(timer); - clearTimeout(fadeTimer); - }; - } - }, [initialFocusedBlockId]); + }); + } else { + pendingFocusRef.current = { blockId, cursor }; + } + }, + [] + ); const registerTextareaRef = useCallback((blockId: string, el: HTMLTextAreaElement | null) => { if (el) { @@ -74,13 +80,19 @@ export const OutlinerTree: React.FC = ({ const target = pendingFocusRef.current; pendingFocusRef.current = null; requestAnimationFrame(() => { + setFocusedBlockId(blockId); el.focus(); - const pos = target.cursor === 'end' ? el.value.length : 0; 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 { // ignore } + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); } } else { @@ -88,23 +100,26 @@ export const OutlinerTree: React.FC = ({ } }, []); - 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 - } - }); - } else { - pendingFocusRef.current = { blockId, cursor }; + 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); + }; } - }, []); + }, [initialFocusedBlockId, blocks.length, focusBlockInput]); // Compute visibility based on collapsed ancestor blocks const visibleEntries = useMemo(() => {