Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
ToStringFunc="@((TestHouse house) => $"{house.Number} - {house.Name}")"
AnchorOrigin="Origin.BottomCenter"
Variant="Variant.Outlined"
aria-label="House Selection"
HelperText="Search by number or name (e.g., '1 -' or 'Test3')"
SearchBoxClearable="true" />
</MudItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
Disabled="@Disabled"
@onclick="@ToggleMenu"
Required="@Required"
ForId="@InputElementId">
ForId="@InputElementId"
@attributes="GetAriaAttributes()">
<InputContent>
<MudInputExtended @ref="_elementReference" InputType="InputType.Hidden" Label="@Label"
Class="@InputClassname" Style="@InputStyle" Margin="@Margin" Placeholder="@Placeholder"
Expand All @@ -30,7 +31,7 @@
Value="@(Strict && !IsValueInList ? null : ReadText)" Underline="@Underline"
Disabled="@GetDisabledState()" ReadOnly="true" Error="@ErrorState.Value" ErrorId="@ErrorIdState.Value"
Clearable="@(Clearable && !GetReadOnlyState())" OnClearButtonClick="(async (e) => await SelectClearButtonClickHandlerAsync(e))"
@attributes="UserAttributes" OnBlur="@OnLostFocus" ShrinkLabel="@(AdornmentStart != null || ShrinkLabel)" Typo="@Typo"
@attributes="GetUserAttributesForHiddenInput()" OnBlur="@OnLostFocus" ShrinkLabel="@(AdornmentStart != null || ShrinkLabel)" Typo="@Typo"
ShowVisualiser="true" DataVisualiserStyle="min-height: 1.1876em; padding-right: 0px;">

<AdornmentStart>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@
/// <summary>
///
/// </summary>
protected Dictionary<T, MudSelectItemExtended<T?>> _valueLookup = new();

Check warning on line 49 in src/CodeBeam.MudBlazor.Extensions/Components/SelectExtended/MudSelectExtended.razor.cs

View workflow job for this annotation

GitHub Actions / build

The type 'T' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'T' doesn't match 'notnull' constraint.
/// <summary>
///
/// </summary>
protected Dictionary<T, MudSelectItemExtended<T?>> _shadowLookup = new();

Check warning on line 53 in src/CodeBeam.MudBlazor.Extensions/Components/SelectExtended/MudSelectExtended.razor.cs

View workflow job for this annotation

GitHub Actions / build

The type 'T' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'T' doesn't match 'notnull' constraint.
private MudInputExtended<string> _elementReference = new();
internal bool _isOpen;
/// <summary>
Expand Down Expand Up @@ -1332,5 +1332,47 @@
var n = ToStringFunc(input);
return ToStringFunc(input);
}

/// <summary>
/// Filters UserAttributes to exclude ARIA attributes so they don't get applied to the hidden input.
/// ARIA attributes should only be on the visible MudInputControl, not the hidden MudInputExtended.
/// </summary>
/// <returns>A dictionary of user attributes with ARIA attributes removed</returns>
protected Dictionary<string, object?> GetUserAttributesForHiddenInput()
{
if (UserAttributes == null || UserAttributes.Count == 0)
return [];

var filtered = new Dictionary<string, object?>(UserAttributes);
filtered.Remove("aria-label");
filtered.Remove("aria-labelledby");
return filtered;
}

/// <summary>
/// Extracts ARIA attributes from UserAttributes that should be applied to the visible control.
/// These attributes belong on the visible MudInputControl, not the hidden MudInputExtended.
/// </summary>
/// <returns>A dictionary containing only ARIA attributes</returns>
protected Dictionary<string, object?> GetAriaAttributes()
{
if (UserAttributes == null || UserAttributes.Count == 0)
return [];

var aria = new Dictionary<string, object?>();

// Only include aria-* attributes that should be on the visible element
var ariaKeys = new[] { "aria-label", "aria-labelledby" };

foreach (var key in ariaKeys)
{
if (UserAttributes.TryGetValue(key, out var value))
{
aria[key] = value;
}
}

return aria;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@namespace MudExtensions.UnitTests.TestComponents

<MudPopoverProvider></MudPopoverProvider>

<MudSelectExtended T="string" @bind-Value="value" @attributes="UserAttributes">
<MudSelectItemExtended Value="@("1")"/>
<MudSelectItemExtended Value="@("2")"/>
<MudSelectItemExtended Value="@("3")"/>
</MudSelectExtended>

@code {
[Parameter]
public Dictionary<string, object?> UserAttributes { get; set; } = new();

string? value = null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -1354,5 +1354,101 @@ public void Select_Should_NotOpen_WhenParentDisabled()
// The menu should not open
comp.Find("div.mud-popover").ClassList.Should().NotContain("mud-popover-open");
}

[Test]
public void Select_AriaLabel_ShouldBeOnVisibleControl_NotHiddenInput()
{
var comp = Context.Render<SelectWithAriaLabelTest>(parameters =>
{
parameters.Add(p => p.UserAttributes, new Dictionary<string, object?> { { "aria-label", "Test Dropdown" } });
});

var visibleControl = comp.Find("div.mud-input-control");
var hiddenInput = comp.Find("input[type='hidden']");

// aria-label should be on the visible MudInputControl
visibleControl.GetAttribute("aria-label").Should().Be("Test Dropdown");

// aria-label should NOT be on the hidden input
hiddenInput.GetAttribute("aria-label").Should().BeNull();
}

[Test]
public void Select_AriaLabelledBy_ShouldBeOnVisibleControl_NotHiddenInput()
{
var comp = Context.Render<SelectWithAriaLabelTest>(parameters =>
{
parameters.Add(p => p.UserAttributes, new Dictionary<string, object?> { { "aria-labelledby", "my-label-id" } });
});

var visibleControl = comp.Find("div.mud-input-control");
var hiddenInput = comp.Find("input[type='hidden']");

// aria-labelledby should be on the visible MudInputControl
visibleControl.GetAttribute("aria-labelledby").Should().Be("my-label-id");

// aria-labelledby should NOT be on the hidden input
hiddenInput.GetAttribute("aria-labelledby").Should().BeNull();
}

[Test]
public void Select_OtherUserAttributes_ShouldBePreservedOnHiddenInput()
{
var comp = Context.Render<SelectWithAriaLabelTest>(parameters =>
{
parameters.Add(p => p.UserAttributes, new Dictionary<string, object?>
{
{ "aria-label", "Test Dropdown" },
{ "data-testid", "my-select" },
{ "custom-attr", "custom-value" }
});
});

var hiddenInput = comp.Find("input[type='hidden']");

// aria-label should be filtered out
hiddenInput.GetAttribute("aria-label").Should().BeNull();

// Other attributes should be preserved
hiddenInput.GetAttribute("data-testid").Should().Be("my-select");
hiddenInput.GetAttribute("custom-attr").Should().Be("custom-value");
}

[Test]
public void Select_BothAriaAttributes_ShouldBeFiltered()
{
var comp = Context.Render<SelectWithAriaLabelTest>(parameters =>
{
parameters.Add(p => p.UserAttributes, new Dictionary<string, object?>
{
{ "aria-label", "Test Dropdown" },
{ "aria-labelledby", "my-label-id" }
});
});

var visibleControl = comp.Find("div.mud-input-control");
var hiddenInput = comp.Find("input[type='hidden']");

// Both aria attributes should be on visible control
visibleControl.GetAttribute("aria-label").Should().Be("Test Dropdown");
visibleControl.GetAttribute("aria-labelledby").Should().Be("my-label-id");

// Neither should be on hidden input
hiddenInput.GetAttribute("aria-label").Should().BeNull();
hiddenInput.GetAttribute("aria-labelledby").Should().BeNull();
}

[Test]
public void Select_NoUserAttributes_ShouldWorkCorrectly()
{
var comp = Context.Render<SelectWithAriaLabelTest>();

var visibleControl = comp.Find("div.mud-input-control");
var hiddenInput = comp.Find("input[type='hidden']");

// Neither element should have aria attributes
visibleControl.GetAttribute("aria-label").Should().BeNull();
hiddenInput.GetAttribute("aria-label").Should().BeNull();
}
}
}
}
Loading