Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -42,62 +42,80 @@

@if (Multiline && Type is null or BitInputType.Text)
{
<textarea @ref="InputElement" @attributes="InputHtmlAttributes"
@onclick="@HandleOnClick"
@onkeyup="@HandleOnKeyUp"
@onfocus="@HandleOnFocus"
@onkeydown="@HandleOnKeyDown"
@onfocusin="@HandleOnFocusIn"
@onfocusout="@HandleOnFocusOut"
@onchange="@HandleOnStringValueChangeAsync"
@oninput="@HandleOnStringValueInputAsync"
name="@Name"
id="@_inputId"
rows="@(Rows ?? 1)"
tabindex="@TabIndex"
readonly="@ReadOnly"
required="@Required"
style="@Styles?.Input"
autofocus="@AutoFocus"
maxlength="@MaxLength"
inputmode="@_inputMode"
placeholder="@Placeholder"
value="@CurrentValueAsString"
disabled=@(IsEnabled is false)
class="bit-tfl-inp @Classes?.Input"
aria-label="@AriaLabel"
aria-labelledby="@(Label.HasValue() || LabelTemplate is not null ? _labelId : null)"
aria-describedby="@(Description.HasValue() || DescriptionTemplate is not null ? _descriptionId : null)" />
<div class="bit-tfl-ghw @Classes?.GhostTextWrapper" style="@Styles?.GhostTextWrapper">
<textarea @ref="InputElement" @attributes="InputHtmlAttributes"
@onclick="@HandleOnClick"
@onkeyup="@HandleOnKeyUp"
@onfocus="@HandleOnFocus"
@onkeydown="@HandleOnKeyDown"
@onfocusin="@HandleOnFocusIn"
@onfocusout="@HandleOnFocusOut"
@onchange="@HandleOnStringValueChangeAsync"
@oninput="@HandleOnStringValueInputAsync"
name="@Name"
id="@_inputId"
rows="@(Rows ?? 1)"
tabindex="@TabIndex"
readonly="@ReadOnly"
required="@Required"
style="@Styles?.Input"
autofocus="@AutoFocus"
maxlength="@MaxLength"
inputmode="@_inputMode"
placeholder="@Placeholder"
value="@CurrentValueAsString"
disabled=@(IsEnabled is false)
class="bit-tfl-inp @Classes?.Input"
aria-label="@AriaLabel"
aria-labelledby="@(Label.HasValue() || LabelTemplate is not null ? _labelId : null)"
aria-describedby="@(Description.HasValue() || DescriptionTemplate is not null ? _descriptionId : null)" />
@if (GhostText.HasValue())
{
<div aria-hidden="true"
class="bit-tfl-gho @Classes?.GhostTextOverlay"
style="@Styles?.GhostTextOverlay">
</div>
}
Comment thread
msynk marked this conversation as resolved.
Comment thread
msynk marked this conversation as resolved.
</div>
}
else
{
<input @ref="@InputElement" @attributes="InputHtmlAttributes"
@onclick="@HandleOnClick"
@onfocus="@HandleOnFocus"
@onkeyup="@HandleOnKeyUp"
@onkeydown="@HandleOnKeyDown"
@onfocusin="@HandleOnFocusIn"
@onfocusout="@HandleOnFocusOut"
@onchange="@HandleOnStringValueChangeAsync"
@oninput="@HandleOnStringValueInputAsync"
name="@Name"
id="@_inputId"
type="@_inputType"
readonly="@ReadOnly"
required="@Required"
tabindex="@TabIndex"
style="@Styles?.Input"
autofocus="@AutoFocus"
maxlength="@MaxLength"
inputmode="@_inputMode"
placeholder="@Placeholder"
autocomplete="@AutoComplete"
value="@CurrentValueAsString"
disabled=@(IsEnabled is false)
class="bit-tfl-inp @Classes?.Input"
aria-label="@AriaLabel"
aria-labelledby="@(Label.HasValue() || LabelTemplate is not null ? _labelId : null)"
aria-describedby="@(Description.HasValue() || DescriptionTemplate is not null ? _descriptionId : null)" />
<div class="bit-tfl-ghw @Classes?.GhostTextWrapper" style="@Styles?.GhostTextWrapper">
<input @ref="@InputElement" @attributes="InputHtmlAttributes"
@onclick="@HandleOnClick"
@onfocus="@HandleOnFocus"
@onkeyup="@HandleOnKeyUp"
@onkeydown="@HandleOnKeyDown"
@onfocusin="@HandleOnFocusIn"
@onfocusout="@HandleOnFocusOut"
@onchange="@HandleOnStringValueChangeAsync"
@oninput="@HandleOnStringValueInputAsync"
name="@Name"
id="@_inputId"
type="@_inputType"
readonly="@ReadOnly"
required="@Required"
tabindex="@TabIndex"
style="@Styles?.Input"
autofocus="@AutoFocus"
maxlength="@MaxLength"
inputmode="@_inputMode"
placeholder="@Placeholder"
autocomplete="@AutoComplete"
value="@CurrentValueAsString"
disabled=@(IsEnabled is false)
class="bit-tfl-inp @Classes?.Input"
aria-label="@AriaLabel"
aria-labelledby="@(Label.HasValue() || LabelTemplate is not null ? _labelId : null)"
aria-describedby="@(Description.HasValue() || DescriptionTemplate is not null ? _descriptionId : null)" />
@if (GhostText.HasValue())
{
<div aria-hidden="true"
class="bit-tfl-gho @Classes?.GhostTextOverlay"
style="@Styles?.GhostTextOverlay">
</div>
Comment thread
msynk marked this conversation as resolved.
}
</div>

@if (ShowClearButton && CurrentValue.HasValue())
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;

namespace Bit.BlazorUI;

Expand All @@ -9,6 +9,8 @@ public partial class BitTextField : BitTextInputBase<string?>
{
private bool _hasFocus;
private string? _oldValue;
private string? _oldGhostText;
private DotNetObjectReference<BitTextField>? _dotnetObj;
private string? _inputMode;
private bool _isPasswordRevealed;
private BitInputType? _elementType;
Expand Down Expand Up @@ -98,6 +100,24 @@ public partial class BitTextField : BitTextInputBase<string?>
/// </summary>
[Parameter] public bool FullWidth { get; set; }

/// <summary>
/// The ghost/suggestion text displayed inline after the current cursor position.
/// Update this value from outside (e.g. from an AI or autocomplete suggestion) to show a faded
/// inline suggestion. The user can accept it by pressing Tab or Enter, or clicking/touching the ghost text.
/// </summary>
Comment thread
msynk marked this conversation as resolved.
[Parameter] public string? GhostText { get; set; }

/// <summary>
/// Gets or sets the icon for the reveal password button when password is shown using custom CSS classes for external icon libraries.
/// Takes precedence over <see cref="HidePasswordIconName"/> when both are set.
/// </summary>
[Parameter] public BitIconInfo? HidePasswordIcon { get; set; }

/// <summary>
/// The icon name for the reveal password button when password is shown from the built-in Fluent UI icons.
/// </summary>
[Parameter] public string? HidePasswordIconName { get; set; }

/// <summary>
/// Gets or sets the icon to display using custom CSS classes for external icon libraries.
/// Takes precedence over <see cref="IconName"/> when both are set.
Expand Down Expand Up @@ -186,6 +206,13 @@ public partial class BitTextField : BitTextInputBase<string?>
/// </summary>
[Parameter] public EventCallback<FocusEventArgs> OnFocusOut { get; set; }

/// <summary>
/// Callback invoked when the ghost text is accepted via Tab, Enter, or click/touch.
/// The accepted ghost text string is passed as the argument.
/// Use this to clear or update the GhostText parameter after acceptance.
/// </summary>
[Parameter] public EventCallback<string?> OnGhostTextAccepted { get; set; }

/// <summary>
/// Callback for when a keyboard key is pressed
/// </summary>
Expand All @@ -196,6 +223,12 @@ public partial class BitTextField : BitTextInputBase<string?>
/// </summary>
[Parameter] public EventCallback<KeyboardEventArgs> OnKeyUp { get; set; }

/// <summary>
/// Enables permanent ghost mode that forces the scrollbar-gutter to always be present, preventing layout shift of the ghost text rendering.
/// </summary>
[Parameter, ResetClassBuilder]
public bool PermanentGhost { get; set; }

/// <summary>
/// Input placeholder text.
/// </summary>
Expand Down Expand Up @@ -239,17 +272,6 @@ public partial class BitTextField : BitTextInputBase<string?>
/// </summary>
[Parameter] public string? RevealPasswordIconName { get; set; }

/// <summary>
/// Gets or sets the icon for the reveal password button when password is shown using custom CSS classes for external icon libraries.
/// Takes precedence over <see cref="HidePasswordIconName"/> when both are set.
/// </summary>
[Parameter] public BitIconInfo? HidePasswordIcon { get; set; }

/// <summary>
/// The icon name for the reveal password button when password is shown from the built-in Fluent UI icons.
/// </summary>
[Parameter] public string? HidePasswordIconName { get; set; }

/// <summary>
/// For multiline text, Number of rows.
/// </summary>
Expand Down Expand Up @@ -296,6 +318,19 @@ public partial class BitTextField : BitTextInputBase<string?>



/// <summary>
/// Called by JavaScript when the ghost text is accepted.
/// </summary>
[JSInvokable("OnGhostTextAccepted")]
public async Task _NotifyGhostTextAccepted(string? acceptedText)
{
if (IsEnabled is false || ReadOnly) return;

await OnGhostTextAccepted.InvokeAsync(acceptedText);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.



public void ToggleRevealPassword()
{
_isPasswordRevealed = _isPasswordRevealed is false;
Expand All @@ -322,12 +357,16 @@ protected override void RegisterCssClasses()

ClassBuilder.Register(() => NoBorder ? "bit-tfl-nbd" : string.Empty);

ClassBuilder.Register(() => ReadOnly ? "bit-tfl-rdl" : string.Empty);
Comment thread
msynk marked this conversation as resolved.

ClassBuilder.Register(() => _hasFocus ? $"bit-tfl-fcs {Classes?.Focused}" : string.Empty);

ClassBuilder.Register(() => Required && Label is null ? "bit-tfl-rnl" : string.Empty);

ClassBuilder.Register(() => FullWidth ? "bit-tfl-fwd" : string.Empty);

ClassBuilder.Register(() => PermanentGhost ? "bit-tfl-pgt" : string.Empty);

ClassBuilder.Register(() => Accent switch
{
BitColor.Primary => "bit-tfl-pri",
Expand Down Expand Up @@ -400,9 +439,20 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
{
await _js.BitTextFieldSetupMultilineInput(_Id, InputElement, AutoHeight, PreventEnter);
}

_dotnetObj = DotNetObjectReference.Create(this);
await _js.BitTextFieldSetupGhostText(_Id, InputElement, _dotnetObj);
_oldGhostText = GhostText;
await _js.BitTextFieldSetGhostText(_Id, GhostText ?? string.Empty);
Comment thread
msynk marked this conversation as resolved.
}
else
{
if (GhostText != _oldGhostText)
{
_oldGhostText = GhostText;
await _js.BitTextFieldSetGhostText(_Id, GhostText ?? string.Empty);
}

if (Multiline && AutoHeight && _oldValue != Value)
{
_oldValue = Value;
Expand Down Expand Up @@ -509,13 +559,14 @@ private async Task HandleOnClearButtonClick()
await OnClear.InvokeAsync();
}


protected override async ValueTask DisposeAsync(bool disposing)
{
if (IsDisposed || disposing is false) return;

await base.DisposeAsync(disposing);

_dotnetObj?.Dispose();

try
{
await _js.BitTextFieldDispose(_Id);
Expand Down
Loading
Loading