diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9ae63941f7..bce94b9c8b 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -16,6 +16,7 @@ using Avalonia.Utilities; using Avalonia.Controls.Metadata; using Avalonia.Media.TextFormatting; using Avalonia.Automation.Peers; +using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Threading; namespace Avalonia.Controls @@ -839,9 +840,9 @@ namespace Avalonia.Controls private void PresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { - if(e.Property == TextPresenter.PreeditTextProperty) + if (e.Property == TextPresenter.PreeditTextProperty) { - if(string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string)) + if (string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string)) { PseudoClasses.Set(":empty", false); @@ -962,7 +963,7 @@ namespace Avalonia.Controls return; } - input = RemoveInvalidCharacters(input); + input = SanitizeInputText(input); if (string.IsNullOrEmpty(input)) { @@ -1015,11 +1016,30 @@ namespace Avalonia.Controls } } - private string? RemoveInvalidCharacters(string? text) + private string? SanitizeInputText(string? text) { if (text is null) return null; + if (!AcceptsReturn) + { + var lineBreakStart = 0; + var graphemeEnumerator = new GraphemeEnumerator(text.AsSpan()); + + while (graphemeEnumerator.MoveNext(out var grapheme)) + { + if (grapheme.FirstCodepoint.IsBreakChar) + { + break; + } + + lineBreakStart += grapheme.Length; + } + + // All lines except the first one are discarded when TextBox does not accept Return key + text = text.Substring(0, lineBreakStart); + } + for (var i = 0; i < invalidCharacters.Length; i++) { text = text.Replace(invalidCharacters[i], string.Empty); @@ -2085,7 +2105,7 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - if(_scrollViewer != null) + if (_scrollViewer != null) { var maxHeight = double.PositiveInfinity; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index a53d1dd5a1..4019136221 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -949,6 +949,44 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Insert_Multiline_Text_Should_Accept_Extra_Lines_When_AcceptsReturn_Is_True() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + AcceptsReturn = true + }; + + RaiseTextEvent(target, $"123 {Environment.NewLine}456"); + + Assert.Equal($"123 {Environment.NewLine}456", target.Text); + } + } + + [Fact] + public void Insert_Multiline_Text_Should_Discard_Extra_Lines_When_AcceptsReturn_Is_False() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + AcceptsReturn = false + }; + + RaiseTextEvent(target, $"123 {"\r"}456"); + + Assert.Equal("123 ", target.Text); + + target.Text = ""; + + RaiseTextEvent(target, $"123 {"\r\n"}456"); + + Assert.Equal("123 ", target.Text); + } + } + [Fact] public void Should_Fullfill_MaxLines_Contraint() {