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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="zoft.MauiExtensions.Controls.AutoCompleteEntry" Version="6.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="zoft.MauiExtensions.Controls.AutoCompleteEntry" Version="6.0.0" />
                    
Directory.Packages.props
<PackageReference Include="zoft.MauiExtensions.Controls.AutoCompleteEntry" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add zoft.MauiExtensions.Controls.AutoCompleteEntry --version 6.0.0
                    
#r "nuget: zoft.MauiExtensions.Controls.AutoCompleteEntry, 6.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package zoft.MauiExtensions.Controls.AutoCompleteEntry@6.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=zoft.MauiExtensions.Controls.AutoCompleteEntry&version=6.0.0
                    
Install as a Cake Addin
#tool nuget:?package=zoft.MauiExtensions.Controls.AutoCompleteEntry&version=6.0.0
                    
Install as a Cake Tool

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.

NuGet

📚 Table of Contents

✨ 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 Entry properties 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

📦 View on NuBrowse

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 calls UseMauiCommunityToolkit() 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 use AutoCompleteEntry.

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:

  1. The user types.
  2. TextChangedCommand or TextChanged fires.
  3. Your app filters its data.
  4. 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 control
  • ProgrammaticChange: your code changed Text
  • SuggestionChosen: 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:

  1. Bind ItemsSource to a filtered collection.
  2. Use DisplayMemberPath to control how items appear in the suggestion list.
  3. Use TextMemberPath to control what text is written back into the entry when an item is selected.
  4. Use either:
    • binding-based filtering with TextChangedCommand (recommended)
    • event-based filtering with TextChanged

💡 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
  • SelectedSuggestions defaults to a fresh mutable observable list per control. Assigning null creates another empty list. Read-only and fixed-size lists (including arrays) throw ArgumentException; assignment leaves the previous selection intact.
  • Membership uses object.Equals / GetHashCode through 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 during CollectionChanged, 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 IList is 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 SelectedSuggestion is authoritative; the multiple collection is inactive. In multiple mode the collection is authoritative; assigning SelectedSuggestion is inactive and does not change text or selection.
  • Single → multiple clears the inactive collection and carries only the current non-null SelectedSuggestion, then clears SelectedSuggestion. 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, set SelectionMode first, 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 owning AutoCompleteEntry as their container. Templates must create a fresh MAUI View (not a ViewCell).

  • Custom templates take precedence over DisplayMemberPath. Setting ItemTemplate back to null restores native text rendering through DisplayMemberPath, or ToString() when the path is empty. The public display path and TextMemberPath are 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

  1. Efficient Filtering: Use proper indexing and async operations for large datasets
  2. Template Complexity: Keep ItemTemplates lightweight for smooth scrolling
  3. Data Virtualization: Consider implementing virtualization for very large lists
  4. 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 ItemsSource is properly bound and contains data
  • ✅ Check DisplayMemberPath matches your data model properties
  • ✅ Verify the control has sufficient height to display suggestions

Issue: Selection not working

  • ✅ Confirm SelectedSuggestion binding is two-way
  • ✅ Check TextMemberPath property is correctly set
  • ✅ Ensure the selected item exists in the current ItemsSource

Issue: Custom templates not rendering (Windows)

  • ✅ Return a fresh MAUI View from each template and a concrete, non-null template from each selector.
  • ✅ Bind row properties to the suggestion item's type (including the correct x:DataType for compiled bindings).
  • ✅ Set ItemTemplate to null to use DisplayMemberPath for 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:

Sponsor

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.

  1. Update CHANGELOG.md: move the pending entries from [Unreleased] into a new [X.Y.Z] section.
  2. Commit and push (or merge a PR) to main.
  3. Push a git tag matching the version:
    git tag 1.2.3
    git push origin 1.2.3
    
  4. The Publish package workflow runs automatically and:
    • Extracts the matching [X.Y.Z] release notes from CHANGELOG.md and 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 / .snupkg files.

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 .csproj or .props files.
  • Pull requests to main only run CI when code or project files change; they do not create releases or publish packages.
  • Publishing to NuGet requires the NUGET_API_KEY repository secret.
  • CHANGELOG.md is 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 96 9/28/2026
5.2.0 221 9/18/2026
5.1.0 116 9/16/2026
5.0.0 1,837 5/22/2026
4.0.6 523 4/21/2026
4.0.5 3,745 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
Loading failed

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.