|
4 | 4 | useEffect, |
5 | 5 | useId, |
6 | 6 | useMemo, |
| 7 | + useRef, |
7 | 8 | type KeyboardEvent, |
8 | 9 | type ReactNode, |
9 | 10 | type RefObject, |
@@ -87,6 +88,7 @@ export function AIFileSelector({ |
87 | 88 | hasLeadingResults = false, |
88 | 89 | }: AIFileSelectorProps) { |
89 | 90 | const listboxId = useId(); |
| 91 | + const lastEmittedResultsSignatureRef = useRef<string | null>(null); |
90 | 92 | const [debouncedQuery] = useDebounce(query, 50); |
91 | 93 | const isBackendSearchActive = |
92 | 94 | useBackendSearch && debouncedQuery.trim().length > 0 && canUseBackendFileSearch(rootFolderPath); |
@@ -114,15 +116,24 @@ export function AIFileSelector({ |
114 | 116 |
|
115 | 117 | return flattenFileSearchResults(categorizedFiles); |
116 | 118 | }, [backendHits, categorizedFiles, isBackendSearchActive]); |
| 119 | + const resultFiles = useMemo(() => results.map(({ file }) => file), [results]); |
| 120 | + const resultFilesSignature = useMemo( |
| 121 | + () => resultFiles.map((file) => `${file.path}\0${file.name}`).join("\n"), |
| 122 | + [resultFiles], |
| 123 | + ); |
117 | 124 |
|
118 | 125 | useEffect(() => { |
119 | 126 | if (selectedIndex <= results.length - 1) return; |
120 | 127 | onSelectedIndexChange?.(Math.max(results.length - 1, 0)); |
121 | 128 | }, [onSelectedIndexChange, results.length, selectedIndex]); |
122 | 129 |
|
123 | 130 | useEffect(() => { |
124 | | - onResultsChange?.(results.map(({ file }) => file)); |
125 | | - }, [onResultsChange, results]); |
| 131 | + if (!onResultsChange) return; |
| 132 | + if (lastEmittedResultsSignatureRef.current === resultFilesSignature) return; |
| 133 | + |
| 134 | + lastEmittedResultsSignatureRef.current = resultFilesSignature; |
| 135 | + onResultsChange(resultFiles); |
| 136 | + }, [onResultsChange, resultFiles, resultFilesSignature]); |
126 | 137 |
|
127 | 138 | useEffect(() => { |
128 | 139 | if (!showSearchInput || !autoFocusSearchInput) return; |
|
0 commit comments