diff --git a/UI/Controls/MultiValueAutoCompleteBox.axaml b/UI/Controls/MultiValueAutoCompleteBox.axaml
new file mode 100644
index 0000000..6616642
--- /dev/null
+++ b/UI/Controls/MultiValueAutoCompleteBox.axaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UI/Controls/MultiValueAutoCompleteBox.axaml.cs b/UI/Controls/MultiValueAutoCompleteBox.axaml.cs
new file mode 100644
index 0000000..33f2e31
--- /dev/null
+++ b/UI/Controls/MultiValueAutoCompleteBox.axaml.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Reactive;
+using TagHierarchyManager.UI.ViewModels;
+
+namespace TagHierarchyManager.UI.Controls;
+
+public partial class MultiValueAutoCompleteBox : UserControl
+{
+ public MultiValueAutoCompleteBox()
+ {
+ InitializeComponent();
+ this.MultiValueAutoCompletePopup.PlacementTarget = this.MultiValueAutoCompleteBoxTextBox;
+ this.MultiValueAutoCompleteListBox.ItemsSource = this.FilteredItems;
+ this.MultiValueAutoCompleteBoxTextBox
+ .GetPropertyChangedObservable(BoundsProperty)
+ .Subscribe(new AnonymousObserver(
+ e => this.MultiValueAutoCompleteListBox.Width = ((Rect)e.NewValue!).Width));
+ }
+
+ public static readonly StyledProperty TextProperty =
+ AvaloniaProperty.Register(
+ nameof(Text), defaultValue: string.Empty, defaultBindingMode: BindingMode.TwoWay);
+
+ public static readonly StyledProperty WatermarkProperty =
+ AvaloniaProperty.Register(
+ nameof(Watermark), defaultValue: string.Empty);
+
+ public static readonly StyledProperty?> ItemsSourceProperty =
+ AvaloniaProperty.Register?>(
+ nameof(ItemsSource), defaultValue: []);
+
+ private bool _isTextBoxFocused;
+
+ private bool CanShowPopup => this._isTextBoxFocused && this.FilteredItems.Any();
+
+ public string Text
+ {
+ get => GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ public string Watermark
+ {
+ get => GetValue(WatermarkProperty);
+ set => SetValue(WatermarkProperty, value);
+ }
+
+ public IEnumerable? ItemsSource
+ {
+ get => GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public ObservableCollection FilteredItems { get; set; } = [];
+
+ private string _lastTypedRawText = string.Empty;
+
+ public void TextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
+ {
+ if (sender is not TextBox box) return;
+ var rawText = box.Text ?? string.Empty;
+ if (box.Text == this._lastTypedRawText) return;
+ this._lastTypedRawText = rawText;
+ this.Text = rawText;
+ }
+
+ private void TextBox_OnGotFocus(object? sender, GotFocusEventArgs e)
+ {
+ this._isTextBoxFocused = true;
+ }
+
+ private void TextBox_OnLostFocus(object? sender, RoutedEventArgs e)
+ {
+ this._isTextBoxFocused = false;
+ this.UpdatePopupIsVisible();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ System.Diagnostics.Debug.WriteLine($"CanShowPopup: {this.CanShowPopup}, focused: {this._isTextBoxFocused}, items: {this.FilteredItems.Count}");
+ if (change.Property == TextProperty)
+ {
+ var last = this.Text.Split(';').Last();
+ last = last.Trim();
+ this.RepopulateFilteredItems(last);
+ }
+ }
+
+ private void RepopulateFilteredItems(string itemName)
+ {
+ this.FilteredItems.Clear();
+
+ if (!string.IsNullOrWhiteSpace(itemName) && this.ItemsSource is not null)
+ {
+ var filtered = this.ItemsSource
+ .Where(t => t.CurrentName.Contains(itemName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ foreach (var item in filtered)
+ this.FilteredItems.Add(item);
+
+ }
+
+ this.UpdatePopupIsVisible();
+ }
+
+ private bool _isApplyingSelection;
+ private void UpdatePopupIsVisible()
+ {
+ if (this._isApplyingSelection) return;
+ this.MultiValueAutoCompletePopup.IsOpen = this.CanShowPopup;
+ }
+
+ private void ListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (sender is not ListBox listBox || listBox.SelectedItem is not TagItemViewModel tag) return;
+ this._isApplyingSelection = true;
+ listBox.SelectedItem = null;
+
+ var lastSemicolon = this._lastTypedRawText.LastIndexOf(';');
+ var prefix = lastSemicolon >= 0 ? this._lastTypedRawText[..(lastSemicolon)].TrimEnd() + "; " : string.Empty;
+ var result = $"{prefix}{tag.CurrentName}";
+ this.Text = result;
+ this.MultiValueAutoCompletePopup.IsOpen = false;
+ this.MultiValueAutoCompleteBoxTextBox.Text = result;
+ this.MultiValueAutoCompleteBoxTextBox.CaretIndex = result.Length;
+ this.MultiValueAutoCompleteBoxTextBox.Focus();
+ this._lastTypedRawText = result;
+ this._isApplyingSelection = false;
+ }
+}
\ No newline at end of file