44// Maintainer: Argo Zhang([email protected] ) Website: https://www.blazor.zone 55
66using Microsoft . Extensions . Localization ;
7- using System . Threading ;
87
98namespace BootstrapBlazor . Components ;
109
@@ -90,6 +89,16 @@ public partial class AutoComplete
9089
9190 private List < string > ? _filterItems ;
9291
92+ /// <summary>
93+ /// Tracks the current user input to prevent it from being overwritten
94+ /// </summary>
95+ private string _currentUserInput = string . Empty ;
96+
97+ /// <summary>
98+ /// Flag to track whether we're handling debounced filtering
99+ /// </summary>
100+ private bool _isFiltering = false ;
101+
93102 /// <summary>
94103 /// <inheritdoc/>
95104 /// </summary>
@@ -113,14 +122,23 @@ protected override void OnParametersSet()
113122 LoadingIcon ??= IconTheme . GetIconByKey ( ComponentIcons . LoadingIcon ) ;
114123
115124 Items ??= [ ] ;
125+
126+ // Initialize _currentUserInput with current value if it hasn't been set yet
127+ if ( string . IsNullOrEmpty ( _currentUserInput ) && ! string . IsNullOrEmpty ( CurrentValueAsString ) )
128+ {
129+ _currentUserInput = CurrentValueAsString ;
130+ }
116131 }
117132
118133 /// <summary>
119134 /// Callback method when a candidate item is clicked
120135 /// </summary>
121136 private async Task OnClickItem ( string val )
122137 {
138+ // Update both the CurrentValue and _currentUserInput when an item is clicked
139+ _currentUserInput = val ;
123140 CurrentValue = val ;
141+
124142 if ( OnSelectedItemChanged != null )
125143 {
126144 await OnSelectedItemChanged ( val ) ;
@@ -129,73 +147,53 @@ private async Task OnClickItem(string val)
129147
130148 private List < string > Rows => _filterItems ?? [ .. Items ] ;
131149
132- // Thread-safe tracking using SemaphoreSlim for async compatibility
133- private string _userCurrentInput = string . Empty ;
134- private readonly SemaphoreSlim _semaphore = new SemaphoreSlim ( 1 , 1 ) ;
135-
136150 /// <summary>
137151 /// TriggerFilter method
138152 /// </summary>
139153 /// <param name="val"></param>
140154 [ JSInvokable ]
141155 public override async Task TriggerFilter ( string val )
142156 {
143- // Thread-safe update using SemaphoreSlim
144- await _semaphore . WaitAsync ( ) ;
145157 try
146158 {
147- _userCurrentInput = val ;
148- }
149- finally
150- {
151- _semaphore . Release ( ) ;
152- }
159+ _isFiltering = true ;
160+ // Update our tracking variable
161+ _currentUserInput = val ;
153162
154- // Process filtering
155- if ( OnCustomFilter != null )
156- {
157- var items = await OnCustomFilter ( val ) ;
158- _filterItems = [ .. items ] ;
159- }
160- else if ( string . IsNullOrEmpty ( val ) )
161- {
162- _filterItems = [ .. Items ] ;
163- }
164- else
165- {
166- var comparison = IgnoreCase ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ;
167- var items = IsLikeMatch
168- ? Items . Where ( s => s . Contains ( val , comparison ) )
169- : Items . Where ( s => s . StartsWith ( val , comparison ) ) ;
170- _filterItems = [ .. items ] ;
171- }
163+ // Filter items as usual
164+ if ( OnCustomFilter != null )
165+ {
166+ var items = await OnCustomFilter ( val ) ;
167+ _filterItems = [ .. items ] ;
168+ }
169+ else if ( string . IsNullOrEmpty ( val ) )
170+ {
171+ _filterItems = [ .. Items ] ;
172+ }
173+ else
174+ {
175+ var comparison = IgnoreCase ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ;
176+ var items = IsLikeMatch
177+ ? Items . Where ( s => s . Contains ( val , comparison ) )
178+ : Items . Where ( s => s . StartsWith ( val , comparison ) ) ;
179+ _filterItems = [ .. items ] ;
180+ }
172181
173- if ( DisplayCount != null )
174- {
175- _filterItems = [ .. _filterItems . Take ( DisplayCount . Value ) ] ;
176- }
182+ if ( DisplayCount != null )
183+ {
184+ _filterItems = [ .. _filterItems . Take ( DisplayCount . Value ) ] ;
185+ }
177186
178- // Thread-safe read using SemaphoreSlim
179- await _semaphore . WaitAsync ( ) ;
180- string latestInput ;
181- try
182- {
183- latestInput = _userCurrentInput ;
187+ // Update the bound value to match the user input, triggering proper value change notifications
188+ // This ensures OnValueChanged is triggered while preventing visual disruption
189+ CurrentValue = val ;
190+
191+ // Refresh UI
192+ StateHasChanged ( ) ;
184193 }
185194 finally
186195 {
187- _semaphore . Release ( ) ;
188- }
189-
190- // Only update CurrentValue if this is still the latest input
191- if ( latestInput == val )
192- {
193- CurrentValue = val ;
194-
195- if ( ! ValueChanged . HasDelegate )
196- {
197- StateHasChanged ( ) ;
198- }
196+ _isFiltering = false ;
199197 }
200198 }
201199
@@ -206,8 +204,10 @@ public override async Task TriggerFilter(string val)
206204 [ JSInvokable ]
207205 public override Task TriggerChange ( string val )
208206 {
209- // Only update CurrentValue if the value has actually changed
210- // This prevents overwriting the user's input
207+ // Update our tracking variable
208+ _currentUserInput = val ;
209+
210+ // Update component value and trigger change notifications
211211 if ( CurrentValue != val )
212212 {
213213 CurrentValue = val ;
@@ -218,4 +218,24 @@ public override Task TriggerChange(string val)
218218 }
219219 return Task . CompletedTask ;
220220 }
221+
222+ /// <summary>
223+ /// Override CurrentValueAsString to return the current user input
224+ /// </summary>
225+ protected override string ? FormatValueAsString ( string ? value )
226+ {
227+ // During filtering operations, use what the user is actually typing
228+ if ( _isFiltering )
229+ {
230+ return _currentUserInput ;
231+ }
232+
233+ // In non-filtering scenarios, sync our tracked value with the component value
234+ if ( ! string . IsNullOrEmpty ( value ) && _currentUserInput != value )
235+ {
236+ _currentUserInput = value ;
237+ }
238+
239+ return base . FormatValueAsString ( value ) ;
240+ }
221241}
0 commit comments