Switch Blazor input bind to oninput instead of onchange (#2147)

This commit is contained in:
Leendert de Borst
2026-06-08 13:33:40 +02:00
committed by Leendert de Borst
parent 301aed7034
commit 71160ba830
8 changed files with 48 additions and 15 deletions

View File

@@ -1,12 +1,14 @@
@using System.Linq.Expressions
<InputText @attributes="AdditionalAttributes"
id="@Id"
Value="@Value"
ValueChanged="ValueChanged"
ValueExpression="ValueExpression"
placeholder="@Placeholder"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
@* Thin styled wrapper around InputTextOnInput so the auth forms get consistent styling without repeating the long
Tailwind class list. The actual bind-on-input logic lives in InputTextOnInput. *@
<InputTextOnInput @attributes="AdditionalAttributes"
id="@Id"
Value="Value"
ValueChanged="ValueChanged"
ValueExpression="ValueExpression"
placeholder="@Placeholder"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
@code {
/// <summary>

View File

@@ -68,7 +68,7 @@ else if (_showLoginWithRecoveryCodeStep)
<DataAnnotationsValidator/>
<div>
<label for="two-factor-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["RecoveryCodeLabel"]</label>
<InputText @bind-Value="_loginModelRecoveryCode.RecoveryCode" id="recovery-code" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" autocomplete="off"/>
<InputTextOnInput @bind-Value="_loginModelRecoveryCode.RecoveryCode" id="recovery-code" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" autocomplete="off"/>
<ValidationMessage For="() => _loginModelRecoveryCode.RecoveryCode" class="text-red-600 dark:text-red-400 text-sm mt-1"/>
</div>
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@Localizer["LoginButton"]</button>

View File

@@ -55,12 +55,12 @@
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">@Localizer["TotpInstructions"]</p>
<div class="mb-4">
<label for="totp-name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["NameOptionalLabel"]</label>
<InputText id="totp-name" @bind-Value="NewTotpCode.Name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" />
<InputTextOnInput id="totp-name" @bind-Value="NewTotpCode.Name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" />
<ValidationMessage For="@(() => NewTotpCode.Name)" class="text-red-600 dark:text-red-400 text-sm mt-1" />
</div>
<div class="mb-4">
<label for="totp-secret" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["SecretKeyLabel"]</label>
<InputText id="totp-secret" @bind-Value="NewTotpCode.SecretKey" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="@Localizer["SecretKeyPlaceholder"]" />
<InputTextOnInput id="totp-secret" @bind-Value="NewTotpCode.SecretKey" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="@Localizer["SecretKeyPlaceholder"]" />
<ValidationMessage For="@(() => NewTotpCode.SecretKey)" class="text-red-600 dark:text-red-400 text-sm mt-1" />
</div>
<div class="flex justify-end">

View File

@@ -33,7 +33,7 @@
<EditForm Model="@_usernameModel" OnSubmit="@ConfirmUsername">
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ResetVaultConfirmUsernameLabel"]</label>
<InputText id="username" @bind-Value="_usernameModel.Username" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
<InputTextOnInput id="username" @bind-Value="_usernameModel.Username" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
</div>
<div class="flex space-x-3">
@@ -59,7 +59,7 @@
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ResetVaultEnterPasswordLabel"]</label>
<InputText id="password" type="password" @bind-Value="_passwordModel.Password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
<InputTextOnInput id="password" type="password" @bind-Value="_passwordModel.Password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
</div>
<div class="flex space-x-3">

View File

@@ -38,7 +38,7 @@
<EditForm Model="@_usernameModel" OnSubmit="@ConfirmUsername">
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ConfirmUsernameLabel"]</label>
<InputText id="username" @bind-Value="_usernameModel.Username" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
<InputTextOnInput id="username" @bind-Value="_usernameModel.Username" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
</div>
<div class="flex space-x-3">
@@ -66,7 +66,7 @@
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["EnterPasswordLabel"]</label>
<InputText id="password" type="password" @bind-Value="_passwordModel.Password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
<InputTextOnInput id="password" type="password" @bind-Value="_passwordModel.Password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
</div>
<div class="flex space-x-3">

View File

@@ -38,7 +38,7 @@ else
<EditForm Model="@VerifyModel" OnValidSubmit="@VerifySetup" class="space-y-4" av-enable="true" av-suppress-save="true">
<div>
<InputText id="verificationCode" @bind-Value="VerifyModel.Code"
<InputTextOnInput id="verificationCode" @bind-Value="VerifyModel.Code"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400"
placeholder="@Localizer["VerificationCodePlaceholder"]"
autocomplete="one-time-code"/>

View File

@@ -0,0 +1,30 @@
@using System.Diagnostics.CodeAnalysis
@using Microsoft.AspNetCore.Components.Forms
@inherits InputBase<string?>
@* A drop-in replacement for Blazor's built-in <InputText> that updates the bound value on the `oninput` event
instead of `onchange`. The built-in component only commits the value on blur, which means programmatically filled
values (e.g. password-manager autofill) that never receive a blur are not bound, which can result in a
a "required" validation error on submit. *@
<input @attributes="AdditionalAttributes"
class="@CssClass"
value="@CurrentValueAsString"
@oninput="OnInput" />
@code {
/// <summary>
/// Commits the new value to the EditContext on every input event so autofilled values are captured immediately.
/// </summary>
private void OnInput(ChangeEventArgs e)
{
CurrentValueAsString = e.Value?.ToString();
}
/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, out string? result, [NotNullWhen(false)] out string? validationErrorMessage)
{
result = value;
validationErrorMessage = null;
return true;
}
}

View File

@@ -28,6 +28,7 @@
@using AliasVault.Client.Services.Auth
@using AliasVault.Client.Services.Database
@using AliasVault.Client.Services.JsInterop;
@using AliasVault.Client.Shared.Components
@using AliasVault.RazorComponents
@using AliasVault.RazorComponents.Alerts
@using AliasVault.RazorComponents.Buttons