zoft.MauiExtensions.Controls.AutoCompleteEntry
6.0.0
dotnet add package zoft.MauiExtensions.Controls.AutoCompleteEntry --version 6.0.0
NuGet\Install-Package zoft.MauiExtensions.Controls.AutoCompleteEntry -Version 6.0.0
<PackageReference Include="zoft.MauiExtensions.Controls.AutoCompleteEntry" Version="6.0.0" />
<PackageVersion Include="zoft.MauiExtensions.Controls.AutoCompleteEntry" Version="6.0.0" />
<PackageReference Include="zoft.MauiExtensions.Controls.AutoCompleteEntry" />
paket add zoft.MauiExtensions.Controls.AutoCompleteEntry --version 6.0.0
#r "nuget: zoft.MauiExtensions.Controls.AutoCompleteEntry, 6.0.0"
#:package zoft.MauiExtensions.Controls.AutoCompleteEntry@6.0.0
#addin nuget:?package=zoft.MauiExtensions.Controls.AutoCompleteEntry&version=6.0.0
#tool nuget:?package=zoft.MauiExtensions.Controls.AutoCompleteEntry&version=6.0.0
zoft.MauiExtensions.Controls.AutoCompleteEntry
A .NET MAUI Entry-derived control that shows app-provided suggestions while the user types.
The control is responsible for displaying suggestions, handling selection, and preserving familiar Entry behavior. Your app remains responsible for filtering data and updating ItemsSource.
📚 Table of Contents
- ✨ Features
- 🚀 Getting Started
- 📋 Properties Reference
- 🎯 Basic Usage
- 💡 Usage Examples
- 🏗️ Platform Support Matrix
- 🎨 Advanced Customization
- 🐛 Troubleshooting
- 🚀 Releasing
- 💖 Support
- 🤝 Contributing
- 📄 License
- 🙏 Acknowledgments
✨ Features
- 🔍 Real-time suggestions driven by your app's filtering logic
- 🎨 Custom item templates for rich suggestion rendering
- 🔄 Binding-first and event-based APIs
- 📱 Cross-platform support for Android, iOS, Windows, and MacCatalyst
- 🎯 Entry compatibility with familiar MAUI
Entryproperties and events - ⚙️ Selection and dropdown control through bindable properties
🚀 Getting Started
Installation
Add the NuGet package to your project:
dotnet add package zoft.MauiExtensions.Controls.AutoCompleteEntry
Setup
Register the control in MauiProgram.cs:
using CommunityToolkit.Maui;
using zoft.MauiExtensions.Controls;
namespace AutoCompleteEntry.Sample
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.UseZoftAutoCompleteEntry() // 👈 Add this line
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}
}
Note
UseZoftAutoCompleteEntry()is the only registration required for this control. The sample app also callsUseMauiCommunityToolkit()because the sample uses CommunityToolkit features in other places. If your app already uses CommunityToolkit, keep that call; otherwise it is not required just to useAutoCompleteEntry.
XAML Namespace
Add the control namespace to the XAML file where you want to use the control:
xmlns:zoft="http://zoft.MauiExtensions/Controls"
First working example
<zoft:AutoCompleteEntry
Placeholder="Search for a country"
ItemsSource="{Binding FilteredList}"
DisplayMemberPath="Country"
TextMemberPath="Country"
SelectedSuggestion="{Binding SelectedItem}"
TextChangedCommand="{Binding TextChangedCommand}" />
public sealed class CountryItem
{
public string Group { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
}
public sealed partial class SampleViewModel : ObservableObject
{
private readonly List<CountryItem> _allCountries =
[
new() { Group = "Group A", Country = "Ecuador" },
new() { Group = "Group A", Country = "Netherlands" },
new() { Group = "Group B", Country = "England" }
];
[ObservableProperty]
public partial ObservableCollection<CountryItem> FilteredList { get; set; } = [];
[ObservableProperty]
public partial CountryItem? SelectedItem { get; set; }
[RelayCommand]
private void TextChanged(string? text)
{
var filter = text ?? string.Empty;
FilteredList = new ObservableCollection<CountryItem>(
_allCountries.Where(item =>
item.Country.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
item.Group.Contains(filter, StringComparison.OrdinalIgnoreCase)));
}
}
This is the key pattern for the control:
- The user types.
TextChangedCommandorTextChangedfires.- Your app filters its data.
- Your app assigns the filtered results to
ItemsSource.
For complete working examples, see the sample app in sample\AutoCompleteEntry.Sample.
📋 Properties Reference
Version 6.0.0 introduces multiple selection. See the 6.0 migration guide for selection contracts, Windows dismissal changes, and Android keyboard configuration.
AutoCompleteEntry-Specific Properties
| Property | Type | Default | Description |
|---|---|---|---|
ItemsSource |
IList |
null |
Collection of suggestion items to display |
SelectedSuggestion |
object |
null |
Single-mode selection (two-way binding); inactive in multiple mode |
SelectionMode |
AutoCompleteEntrySelectionMode |
Single |
Opt in to Multiple selection |
SelectedSuggestions |
IList |
Per-instance empty observable collection | Ordered multiple selections (two-way binding); use a mutable observable list |
SelectionSummary |
string (read-only) |
"" |
Full informational selection text, separate from the query |
DisplayMemberPath |
string |
"" |
Property path for displaying items in the suggestion list |
TextMemberPath |
string |
"" |
Property path for the text value when an item is selected |
ItemTemplate |
DataTemplate |
null |
Custom template for rendering suggestion items |
IsSuggestionListOpen |
bool |
false |
Controls whether the suggestion dropdown is open |
UpdateTextOnSelect |
bool |
true |
Whether selecting an item updates the text field in single mode; ignored in multiple mode |
ShowBottomBorder |
bool |
true |
Controls the visibility of the bottom border |
TextChangedCommand |
ICommand |
null |
Command executed when the user types (receives the current text as parameter) |
Inherited Entry Properties
AutoCompleteEntry inherits from Entry, so all standard Entry properties are available:
| Property | Description |
|---|---|
Text |
The current text value |
Placeholder |
Placeholder text when empty |
PlaceholderColor |
Color of the placeholder text |
TextColor |
Color of the input text |
FontSize, FontFamily, FontAttributes |
Text formatting |
IsReadOnly |
Whether the text can be edited |
MaxLength |
Maximum character length |
CursorPosition |
Current cursor position |
ClearButtonVisibility |
When to show the clear button |
HorizontalTextAlignment, VerticalTextAlignment |
Text alignment |
CharacterSpacing |
Spacing between characters |
IsTextPredictionEnabled |
Enable/disable text prediction |
ReturnType |
Keyboard return key type |
Events
| Event | EventArgs | Description |
|---|---|---|
TextChanged |
AutoCompleteEntryTextChangedEventArgs |
Fired when text changes and includes the reason |
SuggestionChosen |
AutoCompleteEntrySuggestionChosenEventArgs |
User activation; multiple mode includes deselection, reported by IsSelected |
SelectionChanged |
AutoCompleteEntrySelectionChangedEventArgs |
Effective selection changes; immutable AddedItems and RemovedItems snapshots |
SuggestionListOpening |
EventArgs |
Populate initial suggestions before a closed search session opens, including when ItemsSource is empty |
CursorPositionChanged |
AutoCompleteEntryCursorPositionChangedEventArgs |
Fired when cursor position changes |
Plus all inherited Entry events: Completed, Focused, Unfocused
Text change reasons
AutoCompleteEntryTextChangedEventArgs.Reason helps you distinguish why TextChanged fired:
UserInput: the user typed in the controlProgrammaticChange: your code changedTextSuggestionChosen: the user picked an item from the suggestions list
TextChangedCommand only runs for UserInput, which makes it the recommended hook for filtering.
🎯 Basic Usage
AutoCompleteEntry does not filter your data source internally. Instead, it acts as a UI shell around your own filtering logic.
The most common setup is:
- Bind
ItemsSourceto a filtered collection. - Use
DisplayMemberPathto control how items appear in the suggestion list. - Use
TextMemberPathto control what text is written back into the entry when an item is selected. - Use either:
- binding-based filtering with
TextChangedCommand(recommended) - event-based filtering with
TextChanged
- binding-based filtering with
💡 Usage Examples
Multiple selection
<zoft:AutoCompleteEntry
SelectionMode="Multiple"
SelectedSuggestions="{Binding SelectedCountries, Mode=TwoWay}"
ItemsSource="{Binding FilteredList}"
DisplayMemberPath="Country"
TextMemberPath="Country"
TextChangedCommand="{Binding TextChangedCommand}"
SuggestionListOpening="Countries_Opening"
ClearButtonVisibility="WhileEditing" />
Use a mutable ObservableCollection<CountryItem> for SelectedCountries. Set the mode before assigning initial selections in code. The control adds a leading checkbox around the existing row content, including custom ItemTemplate and DataTemplateSelector content. Models do not need an IsSelected property. Select or deselect by activating the row or checkbox; arrow keys only highlight, and Enter activates the highlighted row.
Populate initial results explicitly: closing clears the query without calling the filtering command, so the old results might be stale or empty.
private void Countries_Opening(object sender, EventArgs e)
{
var entry = (zoft.MauiExtensions.Controls.AutoCompleteEntry)sender;
ViewModel.FilterList(entry.Text ?? string.Empty);
}
SuggestionListOpening fires once per closed-to-open search transition, before native rows are presented. It also fires for programmatic opening and for single mode (without changing single-mode text). In multiple mode the query is empty before this callback. Replacing or updating ItemsSource inside the callback, or while already open, does not raise another opening event. Empty results keep the search session active; they do not reset the query. Applications still own filtering, loading, cancellation, and stale asynchronous response protection.
While open, Text is the editable query. Toggling suggestions preserves the query, editing focus, and open session. Filtering items out of ItemsSource never deselects them. The clear button clears only the query and follows normal user-input filtering. UpdateTextOnSelect has no effect in multiple mode.
Closing or dismissing the list clears Text programmatically, preserving both Entry.TextChanged and the reason-aware event when the value changes, without executing TextChangedCommand. The closed field displays SelectionSummary, joining selected text in selection order with "; ". Text resolves through TextMemberPath, or ToString() when the path is empty. DisplayMemberPath still controls default row text. The summary is visually ellipsized to fit the field, never assigned to Text, never truncated in state, and never parsed: item labels may contain semicolons. With no selections the ordinary empty/placeholder presentation appears. Reopening retains selection and starts an empty query.
Collection and transition contract
SelectedSuggestionsdefaults to a fresh mutable observable list per control. Assigningnullcreates another empty list. Read-only and fixed-size lists (including arrays) throwArgumentException; assignment leaves the previous selection intact.- Membership uses
object.Equals/GetHashCodethrough the default equality comparer, never display text. Use stable equality and hash codes while selected. Null entries are ignored. Duplicate equal entries in consumer collections count as one effective selection, in first-occurrence order. The control never adds duplicates; deselection removes every equal occurrence. It does not rewrite externally supplied duplicates duringCollectionChanged, avoiding observable-collection reentrancy exceptions. - Observable additions, removals, replacements, moves, and resets immediately refresh checked rows and the summary. Moves and duplicate-only changes produce no added/removed event. Replaced collections are unsubscribed; the remaining subscription does not keep an abandoned control alive.
- Plain mutable
IListis supported for assignment and user toggles. External edits to a non-observable list are not detected; assign a new list instance to publish them. Change bound collections on the UI thread. - In single mode
SelectedSuggestionis authoritative; the multiple collection is inactive. In multiple mode the collection is authoritative; assigningSelectedSuggestionis inactive and does not change text or selection. - Single → multiple clears the inactive collection and carries only the current non-null
SelectedSuggestion, then clearsSelectedSuggestion. Multiple → single keeps the first effective selection (or null), clears the multiple collection, and sets single-mode text from that item (or empty). Both transitions close the session and discard any query. Inactive writes are discarded on conversion, so repeated switching cannot revive stale selections. For preselection, setSelectionModefirst, then assign the collection.
SelectionChanged reports effective changes from user interaction, observable edits, property replacement, and mode conversion. Converting A to the same A produces no change event; converting A/B to A reports B removed. Changes to inactive state do not raise the event. For a multiple-mode user activation, the bound collection updates first, checked rows and summary update next, then SelectionChanged fires, followed by SuggestionChosen. SuggestionChosen.SelectedItem is the activated item and IsSelected is its resulting state; deselection also fires it. Programmatic changes and mode conversion never fire SuggestionChosen. Single-mode activation retains its existing event order and reports IsSelected = true.
Both sample pages offer mode switching, open/close, initial loading, and programmatic selection buttons. The binding page also exercises default/custom/wrapped/selector rows. The event page displays event order and selection state. See implementation decisions and native verification checklist.
Binding-based example
<zoft:AutoCompleteEntry
Placeholder="Search for a country or group"
ItemsSource="{Binding FilteredList}"
TextMemberPath="Country"
DisplayMemberPath="Country"
TextChangedCommand="{Binding TextChangedCommand}"
SelectedSuggestion="{Binding SelectedItem}"
HeightRequest="50" />
ViewModel implementation:
public partial class SampleViewModel : ObservableObject
{
private readonly List<CountryItem> _allCountries = new()
{
new CountryItem { Group = "Group A", Country = "Ecuador" },
new CountryItem { Group = "Group B", Country = "Netherlands" },
// ... more items
};
[ObservableProperty]
private ObservableCollection<CountryItem> _filteredList = new();
[ObservableProperty]
private CountryItem? _selectedItem;
public SampleViewModel()
{
FilteredList = new ObservableCollection<CountryItem>(_allCountries);
}
[RelayCommand]
private void TextChanged(string? text)
{
var filter = text ?? string.Empty;
var filtered = _allCountries.Where(item =>
item.Country.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
item.Group.Contains(filter, StringComparison.OrdinalIgnoreCase));
FilteredList = new ObservableCollection<CountryItem>(filtered);
}
}
public sealed class CountryItem
{
public string Group { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
}
Binding-based example with ItemTemplate
<zoft:AutoCompleteEntry
Placeholder="Search countries with custom display"
ItemsSource="{Binding FilteredList}"
TextMemberPath="Country"
DisplayMemberPath="Country"
TextChangedCommand="{Binding TextChangedCommand}"
SelectedSuggestion="{Binding SelectedItem}"
ShowBottomBorder="{Binding ShowBottomBorder}"
HeightRequest="50">
<zoft:AutoCompleteEntry.ItemTemplate>
<DataTemplate x:DataType="vm:CountryItem">
<Grid ColumnDefinitions="Auto,*"
Padding="12,8"
HeightRequest="44">
<Border Grid.Column="0"
BackgroundColor="Red"
WidthRequest="4"
HeightRequest="28"
StrokeShape="RoundRectangle 2" />
<VerticalStackLayout Grid.Column="1"
Margin="12,0">
<Label Text="{Binding Country}"
FontSize="16"
FontAttributes="Bold"
TextColor="Black" />
<Label Text="{Binding Group}"
FontSize="12"
TextColor="Gray" />
</VerticalStackLayout>
</Grid>
</DataTemplate>
</zoft:AutoCompleteEntry.ItemTemplate>
</zoft:AutoCompleteEntry>
Event-based example
<zoft:AutoCompleteEntry
Placeholder="Search for a country or group"
ItemsSource="{Binding FilteredList}"
TextMemberPath="Country"
DisplayMemberPath="Country"
TextChanged="AutoCompleteEntry_TextChanged"
SuggestionChosen="AutoCompleteEntry_SuggestionChosen"
CursorPositionChanged="AutoCompleteEntry_CursorPositionChanged"
ClearButtonVisibility="WhileEditing"
HeightRequest="50" />
Code-behind implementation:
private void AutoCompleteEntry_TextChanged(object sender, AutoCompleteEntryTextChangedEventArgs e)
{
// Only filter when the user is actually typing
if (e.Reason == AutoCompleteEntryTextChangeReason.UserInput)
{
var autoComplete = sender as AutoCompleteEntry;
ViewModel.FilterList(autoComplete.Text);
}
}
private void AutoCompleteEntry_SuggestionChosen(object sender, AutoCompleteEntrySuggestionChosenEventArgs e)
{
// Handle the selected suggestion
if (e.SelectedItem is CountryItem selectedCountry)
{
ViewModel.SelectedItem = selectedCountry;
// Perform additional actions like navigation or validation
}
}
private void AutoCompleteEntry_CursorPositionChanged(object sender, AutoCompleteEntryCursorPositionChangedEventArgs e)
{
// Track cursor position for analytics or custom behavior
Console.WriteLine($"Cursor moved to position: {e.CursorPosition}");
}
private void AutoCompleteEntry_Completed(object sender, EventArgs e)
{
if (sender is AutoCompleteEntry autoCompleteEntry)
{
// `GetExactMatch` is an app-provided helper, not part of AutoCompleteEntry.
// For example, your ViewModel could implement it by returning the first item
// whose display text exactly matches the current entry text.
ViewModel.SelectedItem = ViewModel.GetExactMatch(autoCompleteEntry.Text);
}
}
Programmatic control examples
// Programmatically open/close the suggestion list
autoCompleteEntry.IsSuggestionListOpen = true;
// Control text updates on selection
autoCompleteEntry.UpdateTextOnSelect = false; // Keep original text when selecting
// Customize appearance
autoCompleteEntry.ShowBottomBorder = false; // Remove bottom border
autoCompleteEntry.ClearButtonVisibility = ClearButtonVisibility.WhileEditing;
// Handle selection programmatically
autoCompleteEntry.SelectedSuggestion = mySelectedItem;
🏗️ Platform Support Matrix
| Feature | Windows | Android | iOS | MacCatalyst | Notes |
|---|---|---|---|---|---|
| Core Functionality | |||||
| Text Input & Filtering | ✅ | ✅ | ✅ | ✅ | Full support |
| ItemsSource Binding | ✅ | ✅ | ✅ | ✅ | Full support |
| Selection Events | ✅ | ✅ | ✅ | ✅ | Full support |
| Multiple Selection | ✅ | ✅ | ✅ | ✅ | Opt-in collection selection, checkbox rows, query sessions, closed summary; see native verification checklist |
| Appearance & Styling | |||||
| ItemTemplate | ✅ | ✅ | ✅ | ✅ | MAUI DataTemplate and DataTemplateSelector |
| ShowBottomBorder | ❌ | ✅ | ✅ | ✅ | Windows: Planned for future release |
| Text Styling | ✅ | ✅ | ✅ | ✅ | Fonts, colors, alignment |
| Behavior | |||||
| UpdateTextOnSelect | ✅ | ✅ | ✅ | ✅ | Full support |
| IsSuggestionListOpen | ✅ | ✅ | ✅ | ✅ | Full support |
| CursorPosition | ✅ | ✅ | ✅ | ✅ | Full support |
| Entry Features | |||||
| ClearButtonVisibility | ✅ | ✅ | ✅ | ✅ | Full support |
| Placeholder | ✅ | ✅ | ✅ | ✅ | Full support |
| IsReadOnly | ✅ | ✅ | ✅ | ✅ | Full support |
| MaxLength | ✅ | ✅ | ✅ | ✅ | Full support |
Legend
- ✅ Fully Supported - Feature works as expected
- ❌ Not Implemented - Feature exists in API but not yet implemented on this platform
- ⚠️ Limited Support - Feature works with some limitations
Android keyboard layout
For scrollable pages, use Android's resize keyboard mode so the page can scroll
the editor into view without panning the entire window. The sample configures this
in its App constructor:
Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.Application.SetWindowSoftInputModeAdjust(
this, Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific.WindowSoftInputModeAdjust.Resize);
The native single-selection dropdown can overlap the editor when the host uses window panning. The control refreshes popup placement as the viewport changes, but does not change the host application's keyboard mode globally.
Windows Platform Notes
In both selection modes, clicking outside the editor and suggestion list dismisses the list, including clicks on non-focusable page background. Clicking the editor reopens it even if focus remained there. Single-mode dismissal preserves text and selection.
ItemTemplate: Renders MAUI views, including compiled bindings and
DataTemplateSelector. Each row's binding context is the original suggestion; selectors receive the owningAutoCompleteEntryas their container. Templates must create a fresh MAUIView(not aViewCell).Custom templates take precedence over
DisplayMemberPath. SettingItemTemplateback tonullrestores native text rendering throughDisplayMemberPath, orToString()when the path is empty. The public display path andTextMemberPathare unchanged.Templates can be replaced at runtime. Rows are created as WinUI realizes them and their handlers are released on unload or template replacement. Templates should be lightweight, passive suggestion content; the native list retains pointer/keyboard selection and focus. Embedded buttons and other interactive row controls do not receive pointer input.
The binding sample includes default, Group/Country, wrapped, and selector presentations, plus an observable-collection update button. Use the wrapped template to check variable row heights and resizing.
ShowBottomBorder: This styling option doesn't affect the Windows presentation currently.
🎨 Advanced Customization
Custom Item Templates
Use ItemTemplate when you want richer suggestion rows than plain text:
<zoft:AutoCompleteEntry.ItemTemplate>
<DataTemplate x:DataType="models:Product">
<Grid ColumnDefinitions="60,*,Auto"
RowDefinitions="Auto,Auto"
Padding="16,12">
<Image Grid.RowSpan="2"
Source="{Binding ImageUrl}"
WidthRequest="50"
HeightRequest="50"
Aspect="AspectFill" />
<Label Grid.Column="1"
Text="{Binding Name}"
FontSize="16"
FontAttributes="Bold" />
<Label Grid.Column="1" Grid.Row="1"
Text="{Binding Category}"
FontSize="12"
TextColor="Gray" />
<Label Grid.Column="2" Grid.RowSpan="2"
Text="{Binding Price, StringFormat='${0:F2}'}"
FontSize="14"
FontAttributes="Bold"
VerticalOptions="Center"
HorizontalOptions="End" />
</Grid>
</DataTemplate>
</zoft:AutoCompleteEntry.ItemTemplate>
Styling and Theming
<Style x:Key="CustomAutoCompleteStyle" TargetType="zoft:AutoCompleteEntry">
<Setter Property="BackgroundColor" Value="{DynamicResource SurfaceColor}" />
<Setter Property="TextColor" Value="{DynamicResource OnSurfaceColor}" />
<Setter Property="PlaceholderColor" Value="{DynamicResource OnSurfaceVariantColor}" />
<Setter Property="FontSize" Value="16" />
<Setter Property="HeightRequest" Value="56" />
<Setter Property="Margin" Value="16,8" />
<Setter Property="ShowBottomBorder" Value="True" />
</Style>
Performance Tips
- Efficient Filtering: Use proper indexing and async operations for large datasets
- Template Complexity: Keep ItemTemplates lightweight for smooth scrolling
- Data Virtualization: Consider implementing virtualization for very large lists
- Debouncing: Implement debouncing in your TextChangedCommand for better UX
// Example: Debounced filtering
private CancellationTokenSource _filterCancellation;
[RelayCommand]
private async Task TextChanged(string text)
{
_filterCancellation?.Cancel();
_filterCancellation = new CancellationTokenSource();
try
{
await Task.Delay(300, _filterCancellation.Token); // Debounce
await FilterItemsAsync(text);
}
catch (TaskCanceledException)
{
// Filtering was cancelled by newer input
}
}
🐛 Troubleshooting
Common Issues
Issue: Suggestions not appearing
- ✅ Ensure
ItemsSourceis properly bound and contains data - ✅ Check
DisplayMemberPathmatches your data model properties - ✅ Verify the control has sufficient height to display suggestions
Issue: Selection not working
- ✅ Confirm
SelectedSuggestionbinding is two-way - ✅ Check
TextMemberPathproperty is correctly set - ✅ Ensure the selected item exists in the current
ItemsSource
Issue: Custom templates not rendering (Windows)
- ✅ Return a fresh MAUI
Viewfrom each template and a concrete, non-null template from each selector. - ✅ Bind row properties to the suggestion item's type (including the correct
x:DataTypefor compiled bindings). - ✅ Set
ItemTemplatetonullto useDisplayMemberPathfor native text display.
Issue: Performance issues with large datasets
- ✅ Implement efficient filtering logic
- ✅ Use proper async/await patterns
- ✅ Consider pagination or virtual scrolling
💖 Support
If you find this project helpful, please consider supporting its development:
Your support helps maintain and improve this project for the entire .NET MAUI community. Thank you! 🙏
🚀 Releasing
The package version is derived automatically from git tags via MinVer. There is no version property to edit manually. Pushing a tag triggers the Publish package workflow automatically.
Stable release flow
Stable releases use tags such as 6.0.0 (no v prefix), with a matching changelog
section. Opening or merging an implementation PR does not itself publish a package.
- Update
CHANGELOG.md: move the pending entries from[Unreleased]into a new[X.Y.Z]section. - Commit and push (or merge a PR) to
main. - Push a git tag matching the version:
git tag 1.2.3 git push origin 1.2.3 - The Publish package workflow runs automatically and:
- Extracts the matching
[X.Y.Z]release notes fromCHANGELOG.mdand injects them into the NuGet package. - Builds the package (MinVer reads the exact tag as the version).
- Validates that the tag and the built package version match.
- Pushes the package to NuGet.org.
- Creates a GitHub Release and attaches the
.nupkg/.snupkgfiles.
- Extracts the matching
Preview release flow
For pre-release packages between stable tags, use a pre-release suffix on the tag:
git tag 1.2.3-preview.1
git push origin 1.2.3-preview.1
MinVer uses the tag as-is, producing a NuGet pre-release package (e.g., 1.2.3-preview.1).
A matching CHANGELOG section is not required; if none is found the package will simply have no release notes.
Manual dispatch
The Publish package workflow can also be triggered manually from the Actions tab:
ref— branch, tag, or SHA to build (default:main).publish_to_nuget— push the built package to NuGet.org.
When triggered manually, the GitHub Release creation and asset upload steps are skipped.
Notes
- The package version comes only from git tags via MinVer — never from a property in
.csprojor.propsfiles. - Pull requests to
mainonly run CI when code or project files change; they do not create releases or publish packages. - Publishing to NuGet requires the
NUGET_API_KEYrepository secret. CHANGELOG.mdis the long-lived human changelog; GitHub Releases are the release-specific summary.
🤝 Contributing
See CONTRIBUTING.md for setup and validation, and AGENTS.md for provider-neutral coding assistant guidance and the repository map.
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
📄 License
This project is licensed under the MIT License - see the LICENSE.md file for details.
🙏 Acknowledgments
- Inspired by platform-native autocomplete controls
- Built with ❤️ for the .NET MAUI community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-android36.0 is compatible. net10.0-browser was computed. net10.0-ios was computed. net10.0-ios26.0 is compatible. net10.0-maccatalyst was computed. net10.0-maccatalyst26.0 is compatible. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net10.0-windows10.0.19041 is compatible. |
-
net10.0
- Microsoft.Maui.Controls (>= 10.0.110)
- zoft.MauiExtensions.Core (>= 6.1.0)
-
net10.0-android36.0
- Microsoft.Maui.Controls (>= 10.0.110)
- zoft.MauiExtensions.Core (>= 6.1.0)
-
net10.0-ios26.0
- Microsoft.Maui.Controls (>= 10.0.110)
- zoft.MauiExtensions.Core (>= 6.1.0)
-
net10.0-maccatalyst26.0
- Microsoft.Maui.Controls (>= 10.0.110)
- Microsoft.Maui.Graphics (>= 10.0.110)
- zoft.MauiExtensions.Core (>= 6.1.0)
-
net10.0-windows10.0.19041
- CommunityToolkit.WinUI.Extensions (>= 8.2.251219)
- Microsoft.Maui.Controls (>= 10.0.110)
- zoft.MauiExtensions.Core (>= 6.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.0.0 | 93 | 9/28/2026 |
| 5.2.0 | 220 | 9/18/2026 |
| 5.1.0 | 116 | 9/16/2026 |
| 5.0.0 | 1,836 | 5/22/2026 |
| 4.0.6 | 523 | 4/21/2026 |
| 4.0.5 | 3,743 | 8/20/2025 |
| 4.0.4 | 539 | 7/11/2025 |
| 4.0.3 | 615 | 5/28/2025 |
| 4.0.2 | 783 | 2/21/2025 |
| 4.0.1 | 338 | 2/18/2025 |
| 3.0.8 | 7,342 | 8/30/2024 |
| 3.0.7 | 315 | 8/29/2024 |
| 3.0.6 | 311 | 8/28/2024 |
| 3.0.5 | 313 | 8/27/2024 |
| 3.0.4 | 351 | 8/26/2024 |
| 3.0.3 | 304 | 8/23/2024 |
| 3.0.2 | 5,597 | 5/24/2024 |
| 3.0.1 | 358 | 5/23/2024 |
| 3.0.0 | 405 | 5/23/2024 |
| 2.0.0 | 10,135 | 4/8/2023 |
Major release. See the [migration guide](docs/migration-6.0.md).
### Added
- Opt-in multiple selection on Android, Windows, iOS, and MacCatalyst: two-way `SelectedSuggestions`, checkbox suggestion rows, persistent query sessions, and an ellipsized selection summary separate from `Text` ([#74](https://github.com/zleao/zoft.MauiExtensions.AutoCompleteEntry/issues/74)).
- `SelectionChanged`, `SuggestionListOpening`, and `SuggestionChosen.IsSelected`, with observable selection synchronization and deterministic mode transitions. Single selection remains the default.
- Scrollable binding/event samples with grouped multiple-selection options, initial-list population, programmatic selection changes, and event-order reporting.
### Changed
- Updated `Microsoft.Maui.Controls` in the library/sample and the MacCatalyst `Microsoft.Maui.Graphics` dependency to `10.0.110`.
- **Breaking behavior (Windows):** outside clicks now dismiss single-selection suggestions even on non-focusable page background; clicking the still-focused editor reopens them. Text and selection are preserved on dismissal.
- Windows native suggestion updates are deferred and coalesced through snapshots rather than applied synchronously during consumer collection notifications.
- The Android sample uses keyboard resize mode with its ScrollViews. Applications using window panning should review the documented keyboard configuration; the control does not change the host's keyboard mode globally.
### Fixed
- iOS and MacCatalyst suggestions become visible when an initially empty observable result collection is populated during an open session, without resetting the query or repeating the opening event.
- Android sample pages resize for the keyboard so suggestions do not cover the editor during window panning. Single-selection dropdowns also refresh their measurements when viewport or anchor bounds change.
- Vertically centered the default Windows multiple-selection suggestion text beside its checkbox.
- Windows multiple-selection suggestions reopen when clicking the still-focused editor after outside dismissal.
- Windows multiple selection preserves the native editor border/background when displaying its summary and uses the native suggestion template's popup styling.
- Windows multiple-selection suggestions use an opaque, theme-aware background. Suggestion updates are deferred through snapshots to prevent reentrant WinUI collection changes when clearing or refiltering the query.
- Android multiple-selection queries no longer reset when filtering produces no results. Popup placement uses available space above or below the editor without covering the keyboard, and selection refresh ignores disposed native rows.