diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/Calendar.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/Calendar.razor
index fac5ed2d..d48ccfe8 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/Calendar.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/Calendar.razor
@@ -128,11 +128,15 @@
margin: 0 auto;
padding: 15px 0;
font-size: 13px;
+ text-align: center;
+ width: 300px;
+ max-width: 100%;
+ box-sizing: border-box;
}
.control-wrapper {
margin: 0 auto;
- padding-top: 20px;
+ width: fit-content;
}
.e-calendar .e-content .e-cell.personal-appointment span.e-day,
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/DateTimePicker.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/DateTimePicker.razor
index a95d1646..62b2ef0e 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/DateTimePicker.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/DateTimePicker.razor
@@ -111,7 +111,7 @@
This sample demonstrates the list of events of the DateTimePicker component which will be triggered for appropriate DateTimePicker actions.
-
+
Event Name
@@ -221,7 +221,7 @@
The DateTimePicker provides the WeekNumber and WeekRule properties to specify the rule for defining the first week of the year with options: FirstDay, FirstFullWeek, and FirstFourDayWeek.
-
+
Types
@@ -315,4 +315,50 @@
.e-calendar .e-content td.e-selected.e-focused-date.e-cell.official-appointment span.e-day {
background-color: rgb(255 255 0 / 90%);
}
+
+ :root {
+ --table-bg: var(--panel-bg);
+ --table-border: rgba(0,0,0,0.06);
+ --table-border-radius: 6px;
+ --table-header-bg: rgba(0,0,0,0.02);
+ --table-header-color: var(--top-text);
+ --table-row-bg: #ffffff;
+ --table-row-border: rgba(0,0,0,0.04);
+ }
+
+ .dark {
+ --table-bg: var(--panel-bg);
+ --table-border: rgba(255,255,255,0.10);
+ --table-header-bg: rgba(255,255,255,0.04);
+ --table-header-color: var(--top-text);
+ --table-row-bg: rgba(255,255,255,0.01);
+ --table-row-border: rgba(255,255,255,0.04);
+ }
+
+ .table {
+ background-color: var(--table-bg, #ffffff);
+ color: var(--text-color, #111827);
+ border: 1px solid var(--table-border, rgba(0,0,0,0.06));
+ border-radius: var(--table-border-radius, 6px);
+ overflow: hidden;
+ border-collapse: separate;
+ border-spacing: 0;
+ width: 100%;
+ }
+
+ .table thead th {
+ background-color: var(--table-header-bg, rgba(0,0,0,0.02));
+ color: var(--table-header-color, var(--top-text, #111827));
+ padding: 0.75rem 1rem;
+ text-align: left;
+ font-weight: 600;
+ border-bottom: 1px solid var(--table-row-border, rgba(0,0,0,0.04));
+ }
+
+ .table tbody td {
+ padding: 0.75rem 1rem;
+ border-top: 1px solid var(--table-row-border, rgba(0,0,0,0.04));
+ background-color: var(--table-row-bg, #ffffff);
+ color: var(--text-color, #111827);
+ }
\ No newline at end of file
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/TimePicker.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/TimePicker.razor
index 5533b089..4615e0ed 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/TimePicker.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Components/Calendars/TimePicker.razor
@@ -139,7 +139,7 @@
This sample demonstrates the list of events of the TimePicker component which will be triggered for appropriate TimePicker actions.
-
+
Event Name
@@ -213,4 +213,50 @@
font-size: 14px;
margin-bottom: 6px;
}
+
+ :root {
+ --table-bg: var(--panel-bg);
+ --table-border: rgba(0,0,0,0.06);
+ --table-border-radius: 6px;
+ --table-header-bg: rgba(0,0,0,0.02);
+ --table-header-color: var(--top-text);
+ --table-row-bg: #ffffff;
+ --table-row-border: rgba(0,0,0,0.04);
+ }
+
+ .dark {
+ --table-bg: var(--panel-bg);
+ --table-border: rgba(255,255,255,0.10);
+ --table-header-bg: rgba(255,255,255,0.04);
+ --table-header-color: var(--top-text);
+ --table-row-bg: rgba(255,255,255,0.01);
+ --table-row-border: rgba(255,255,255,0.04);
+ }
+
+ .table {
+ background-color: var(--table-bg, #ffffff);
+ color: var(--text-color, #111827);
+ border: 1px solid var(--table-border, rgba(0,0,0,0.06));
+ border-radius: var(--table-border-radius, 6px);
+ overflow: hidden;
+ border-collapse: separate;
+ border-spacing: 0;
+ width: 100%;
+ }
+
+ .table thead th {
+ background-color: var(--table-header-bg, rgba(0,0,0,0.02));
+ color: var(--table-header-color, var(--top-text, #111827));
+ padding: 0.75rem 1rem;
+ text-align: left;
+ font-weight: 600;
+ border-bottom: 1px solid var(--table-row-border, rgba(0,0,0,0.04));
+ }
+
+ .table tbody td {
+ padding: 0.75rem 1rem;
+ border-top: 1px solid var(--table-row-border, rgba(0,0,0,0.04));
+ background-color: var(--table-row-bg, #ffffff);
+ color: var(--text-color, #111827);
+ }
\ No newline at end of file
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/Calendar/SpecialDatesDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/Calendar/SpecialDatesDemo.razor
index 071cabd6..4a0f6183 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/Calendar/SpecialDatesDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/Calendar/SpecialDatesDemo.razor
@@ -30,12 +30,12 @@
var CurrentMonth = CurrentDate.Value.Month;
if (args.Value.Value.Month == CurrentMonth && (args.Value.Value.Day == 7 || args.Value.Value.Day == 14 || args.Value.Value.Day == 24 || args.Value.Value.Day == 29))
{
- this.SelectedValue = this.SelectedDate?.ToString("M / d / yyyy") + "(Personal appointment)";
+ this.SelectedValue = this.SelectedDate?.ToString("M / d / yyyy") + " (Personal appointment)";
Count++;
}
if (args.Value.Value.Month == CurrentMonth && (args.Value.Value.Day == 3 || args.Value.Value.Day == 11 || args.Value.Value.Day == 17 || args.Value.Value.Day == 22))
{
- this.SelectedValue = this.SelectedDate?.ToString("M / d / yyyy") + "(Official appointment)";
+ this.SelectedValue = this.SelectedDate?.ToString("M / d / yyyy") + " (Official appointment)";
Count++;
}
if (Count == 0)
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DatePicker/ViewsDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DatePicker/ViewsDemo.razor
index a5624d4f..d4617c40 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DatePicker/ViewsDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DatePicker/ViewsDemo.razor
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DateTimePicker/DateTimeFormatDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DateTimePicker/DateTimeFormatDemo.razor
index 59a1314e..03ab1cd5 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DateTimePicker/DateTimeFormatDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Calendars/DateTimePicker/DateTimeFormatDemo.razor
@@ -1,4 +1,4 @@
-
+
@code {
public DateTime? FormattedDateValue { get; set; } = new DateTime(2026, 12, 11, 11, 30, 00);
}
\ No newline at end of file
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/BubbleAndScatterCharts/ScatterChart/DataLabelDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/BubbleAndScatterCharts/ScatterChart/DataLabelDemo.razor
index ac09a1c1..56158ece 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/BubbleAndScatterCharts/ScatterChart/DataLabelDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/BubbleAndScatterCharts/ScatterChart/DataLabelDemo.razor
@@ -11,7 +11,9 @@
-
+
+
+
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/DataLabelDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/DataLabelDemo.razor
index 4b4e5a75..8a1a259d 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/DataLabelDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/DataLabelDemo.razor
@@ -18,7 +18,9 @@
Fill="#8AA624"
ColumnSpacing="0.3">
-
+
+
+
@@ -32,7 +34,9 @@
Fill="#D25D5D"
ColumnSpacing="0.3">
-
+
+
+
@@ -46,7 +50,9 @@
Fill="#239BA7"
ColumnSpacing="0.3">
-
+
+
+
@@ -69,6 +75,9 @@
_currentTheme = CurrentTheme;
}
+ private bool IsDarkTheme => _currentTheme.ToString().Contains("Dark") || _currentTheme.ToString().Contains("HighContrast");
+ private string TextColor => IsDarkTheme ? "#FFFFFF" : "#000000";
+
public class PhonePalette
{
public string Year { get; set; } = string.Empty;
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/StackingLabelsDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/StackingLabelsDemo.razor
index 66a2263c..211705ff 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/StackingLabelsDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/BarChart/StackingLabelsDemo.razor
@@ -7,7 +7,9 @@
-
+
+
+
@@ -56,6 +58,9 @@
_currentTheme = CurrentTheme;
}
+ private bool IsDarkTheme => _currentTheme.ToString().Contains("Dark") || _currentTheme.ToString().Contains("HighContrast");
+ private string TextColor => IsDarkTheme ? "#FFFFFF" : "#000000";
+
public class AnnualEnergyDataLabel
{
public string X { get; set; } = string.Empty;
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/ColumnChart/StackingLabelsDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/ColumnChart/StackingLabelsDemo.razor
index 575f6f87..d726c5c0 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/ColumnChart/StackingLabelsDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/ColumnAndBarCharts/ColumnChart/StackingLabelsDemo.razor
@@ -2,9 +2,10 @@
-
-
+
+
+
@@ -24,6 +25,9 @@
_currentTheme = CurrentTheme;
}
+ private bool IsDarkTheme => _currentTheme.ToString().Contains("Dark") || _currentTheme.ToString().Contains("HighContrast");
+ private string TextColor => IsDarkTheme ? "#FFFFFF" : "#000000";
+
public class StackingColumnData
{
public string X { get; set; } = string.Empty;
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/LineCharts/SplineChart/DataLabelDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/LineCharts/SplineChart/DataLabelDemo.razor
index 644f3940..a1e72ee8 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/LineCharts/SplineChart/DataLabelDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Charts/LineCharts/SplineChart/DataLabelDemo.razor
@@ -28,7 +28,9 @@
Fill="#FF7F50"
Type="ChartSeriesType.Spline">
-
+
+
+
@@ -40,7 +42,9 @@
Fill="#19DA00"
Type="ChartSeriesType.Spline">
-
+
+
+
@@ -57,6 +61,9 @@
_currentTheme = CurrentTheme;
}
+ private bool IsDarkTheme => _currentTheme.ToString().Contains("Dark") || _currentTheme.ToString().Contains("HighContrast");
+ private string TextColor => IsDarkTheme ? "#FFFFFF" : "#000000";
+
public class SplinePoint
{
public string X { get; set; } = string.Empty;
diff --git a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Inputs/Uploader/TemplateDemo.razor b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Inputs/Uploader/TemplateDemo.razor
index f31a99bc..1cf49438 100644
--- a/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Inputs/Uploader/TemplateDemo.razor
+++ b/samples/Blazor.Toolkit.Samples.Client/Pages/Demos/Inputs/Uploader/TemplateDemo.razor
@@ -6,7 +6,7 @@
File Name : @context.Name
File Size : @context.Size
-
File Status : @context.Status
+
File Status : @context.Status
diff --git a/src/Base/SfThemeRoot.Payload.cs b/src/Base/SfThemeRoot.Payload.cs
index bafc40ea..d9cbed59 100644
--- a/src/Base/SfThemeRoot.Payload.cs
+++ b/src/Base/SfThemeRoot.Payload.cs
@@ -1499,13 +1499,6 @@ .eJSVGTooltip tspan {
z-index: 1;
}
- .e-btn-group input:hover + label.e-btn,
- .e-btn-group input:focus + label.e-btn,
- .e-btn-group label.e-btn.e-active {
- border-color: Highlight !important;
- color: Highlight !important;
- }
-
.e-btn-group input[aria-pressed] + label.e-btn {
border-color: Highlight !important;
color: Highlight !important;
@@ -1529,6 +1522,13 @@ .eJSVGTooltip tspan {
forced-color-adjust: none;
}
+ .e-btn-group input:checked + label.e-btn:not(.e-active):not(.e-disabled):not(.e-outline) {
+ background-color: ButtonFace !important;
+ border-color: ButtonText !important;
+ color: ButtonText !important;
+ forced-color-adjust: none;
+ }
+
.e-btn-group input + label:hover {
border-color: Highlight !important;
color: Highlight !important;
@@ -1537,7 +1537,8 @@ .eJSVGTooltip tspan {
forced-color-adjust: none;
}
- .e-btn-group input:hover + label.e-btn:not(.e-disabled):not(.e-outline) {
+ .e-btn-group input:hover + label.e-btn:not(.e-disabled):not(.e-outline),
+ .e-btn-group input:hover:checked + label.e-btn:not(.e-active):not(.e-disabled):not(.e-outline) {
background-color: HighlightText !important;
border-color: Highlight !important;
color: Highlight !important;
@@ -1545,14 +1546,6 @@ .eJSVGTooltip tspan {
forced-color-adjust: none;
}
- .e-btn-group input:checked + label.e-btn:not(.e-disabled):not(.e-outline) {
- background-color: Highlight !important;
- border-color: Highlight !important;
- color: HighlightText !important;
- box-shadow: none !important;
- forced-color-adjust: none;
- }
-
.e-switch-wrapper .e-switch-handle {
background-color: ButtonText !important;
forced-color-adjust: none !important;
diff --git a/src/Components/Buttons/ButtonGroup/Button.razor b/src/Components/Buttons/ButtonGroup/Button.razor
index dcbab732..ba112de7 100644
--- a/src/Components/Buttons/ButtonGroup/Button.razor
+++ b/src/Components/Buttons/ButtonGroup/Button.razor
@@ -36,7 +36,7 @@ else
@onchange="HandleInputChangeAsync"
@attributes="HtmlAttributes">
-
+
@if (!string.IsNullOrEmpty(IconCss) && (IconPosition == IconPosition.Left || IconPosition == IconPosition.Top))
{
diff --git a/src/Components/Buttons/ButtonGroup/Button.razor.cs b/src/Components/Buttons/ButtonGroup/Button.razor.cs
index 46c8bea3..66be1b2c 100644
--- a/src/Components/Buttons/ButtonGroup/Button.razor.cs
+++ b/src/Components/Buttons/ButtonGroup/Button.razor.cs
@@ -71,11 +71,13 @@ private string GetSelectionCssClass()
///
///
- /// Handles keyboard interactions on the input element, specifically Space key activation.
+ /// Handles keyboard interactions on the input element.
///
///
- /// Space key activation is necessary because native radio/checkbox inputs respond to Enter,
- /// not Space. This handler ensures the button activates when Space is pressed while focused.
+ /// In single-selection (radio) mode, arrow keys natively move focus and change
+ /// the checked radio. We mark that interaction so
+ /// can ignore it — selection should only change on Space, Enter, or mouse click.
+ /// Space is handled natively (raises change ). Enter is handled here.
///
/// The keyboard event arguments.
private async Task HandleInputKeyDownAsync(KeyboardEventArgs args)
@@ -85,19 +87,39 @@ private async Task HandleInputKeyDownAsync(KeyboardEventArgs args)
return;
}
- // Native radio/checkbox inputs respond to Space (toggle) and Enter (default click on a
- // wrapping label). Both should trigger the same selection flow.
- if (args.Code == "Space" || args.Key == " " || args.Key == "Enter")
+ // Single: arrow keys move focus only — ignore the following native change
+ if (ButtonGroup?.Mode == SelectionMode.Single && IsArrowKey(args))
{
+ ButtonGroup._suppressChangeFromArrowKey = true;
+ return;
+ }
+
+ // Space / Enter for both Single and Multiple
+ // (bUnit only fires keydown; browser would also fire change — suppress that)
+ if (args.Code == "Space" || args.Key == " " || args.Key == "Enter" || args.Code == "Enter")
+ {
+#if NET10_0_OR_GREATER
+ ButtonGroup?._suppressChangeFromArrowKey = true;
+#else
+ if (ButtonGroup is not null)
+ {
+ ButtonGroup._suppressChangeFromArrowKey = true;
+ }
+#endif
await ClickHandlerAsync().ConfigureAwait(false);
}
}
+ private static bool IsArrowKey(KeyboardEventArgs args)
+ {
+ return args.Key is "ArrowLeft" or "ArrowRight" or "ArrowUp" or "ArrowDown"
+ || args.Code is "ArrowLeft" or "ArrowRight" or "ArrowUp" or "ArrowDown";
+ }
+
///
///
- /// Handles native change events from the underlying input element so the selection state
- /// is kept in sync with the DOM in scenarios where the browser changes the input state without
- /// going through (e.g., assistive technology interactions).
+ /// Handles native change events from the underlying input element.
+ /// This is the primary selection path for mouse clicks (via label[for]) and Space key.
///
/// The change event arguments from the input.
private async Task HandleInputChangeAsync(ChangeEventArgs args)
@@ -107,40 +129,55 @@ private async Task HandleInputChangeAsync(ChangeEventArgs args)
return;
}
- if (args?.Value is null)
+ // Arrow navigation in Single mode (flag is on the group — change may be on another Button)
+ // Ignore change after arrow (Single) or Space/Enter (already handled in keydown).
+ // Prevents double-toggle in the browser; bUnit never fires change on KeyDown.
+ if (ButtonGroup._suppressChangeFromArrowKey)
{
+ ButtonGroup._suppressChangeFromArrowKey = false;
+ ButtonGroup.RefreshButtonsUi();
return;
}
- bool newValue;
- try
- {
- newValue = Convert.ToBoolean(args.Value, System.Globalization.CultureInfo.InvariantCulture);
- }
- catch (InvalidCastException)
- {
- return;
- }
- catch (FormatException)
+ // Click / Space already handled this interaction
+ if (ButtonGroup._isClicked)
{
return;
}
- // Only update the state when the DOM-driven value diverges from the current value.
- // This avoids re-entering the click flow and keeps two-way binding stable.
- if (newValue != Selected)
+ ButtonGroup._isClicked = true;
+
+ if (ButtonGroup.Mode == SelectionMode.Multiple)
{
- ButtonGroup._isClicked = true;
- if (ButtonGroup.Mode == SelectionMode.Multiple)
+ if (args?.Value is null)
{
- await UpdateButtonStateAsync(newValue).ConfigureAwait(false);
+ return;
+ }
+
+ bool newValue;
+ try
+ {
+ newValue = Convert.ToBoolean(args.Value, System.Globalization.CultureInfo.InvariantCulture);
+ }
+ catch (InvalidCastException)
+ {
+ return;
+ }
+ catch (FormatException)
+ {
+ return;
}
- else if (ButtonGroup.Mode == SelectionMode.Single && newValue)
+
+ if (newValue != Selected)
{
- await ButtonGroup.ClearSiblingsAsync(this).ConfigureAwait(false);
- await UpdateButtonStateAsync(true).ConfigureAwait(false);
+ await UpdateButtonStateAsync(newValue).ConfigureAwait(false);
}
}
+ else if (ButtonGroup.Mode == SelectionMode.Single)
+ {
+ await ButtonGroup.ClearSiblingsAsync(this).ConfigureAwait(false);
+ await UpdateButtonStateAsync(true).ConfigureAwait(false);
+ }
}
///
@@ -154,7 +191,14 @@ private async Task ClickHandlerAsync()
return;
}
+ // Another handler already processed this interaction
+ if (ButtonGroup._isClicked)
+ {
+ return;
+ }
+
ButtonGroup._isClicked = true;
+
if (ButtonGroup.Mode == SelectionMode.Multiple)
{
await UpdateButtonStateAsync(!Selected).ConfigureAwait(false);
@@ -179,7 +223,24 @@ private async Task ClickHandlerAsync()
/// The new selection state to apply.
internal async Task UpdateButtonStateAsync(bool state)
{
- Selected = _selected = _buttonSelected = await SfBaseUtils.UpdatePropertyAsync(state, _selected, SelectedChanged).ConfigureAwait(false);
+ bool previous = _selected;
+
+ // Synchronous update first — prevents race with @onchange during await
+ _selected = state;
+ _buttonSelected = state;
+ Selected = state;
+
+ Selected = _selected = _buttonSelected =
+ await SfBaseUtils.UpdatePropertyAsync(state, previous, SelectedChanged).ConfigureAwait(false);
+ }
+
+ ///
+ ///
+ /// Triggers a re-render so checked="@_selected" is applied to the DOM.
+ ///
+ internal void NotifyStateChanged()
+ {
+ StateHasChanged();
}
#endregion
diff --git a/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.cs b/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.cs
index 5e619239..20363963 100644
--- a/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.cs
+++ b/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.cs
@@ -40,6 +40,13 @@ public partial class SfButtonGroup
///
internal bool _isClicked;
+ ///
+ ///
+ /// When true, the next input change in single-selection mode is ignored.
+ /// Set on arrow keydown (focused button) and read on change (may be a different button).
+ ///
+ internal bool _suppressChangeFromArrowKey;
+
#endregion
#region Helper Methods
@@ -117,6 +124,24 @@ internal async Task ClearSiblingsAsync(Button except)
}
}
+ ///
+ ///
+ /// Re-renders all child buttons so checked is restored from bound selection
+ /// after the browser natively moves the radio selection (e.g. arrow keys).
+ ///
+ internal void RefreshButtonsUi()
+ {
+ if (_buttonItems is null)
+ {
+ return;
+ }
+
+ foreach (Button item in _buttonItems)
+ {
+ item.NotifyStateChanged();
+ }
+ }
+
///
///
/// Builds the CSS class string for the ButtonGroup based on configuration.
diff --git a/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.css b/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.css
index 2aae7ea3..2b0e094c 100644
--- a/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.css
+++ b/src/Components/Buttons/ButtonGroup/SfButtonGroup.razor.css
@@ -235,6 +235,19 @@
border-top-right-radius: 0;
}
+/* Keep focus/hover border visible in stacked (vertical) and horizontal groups */
+.e-btn-group ::deep .e-btn:hover,
+.e-btn-group ::deep .e-btn:focus,
+.e-btn-group ::deep .e-btn:focus-visible,
+.e-btn-group ::deep .e-btn:active,
+.e-btn-group ::deep input:hover + label.e-btn,
+.e-btn-group ::deep input:focus + label.e-btn,
+.e-btn-group ::deep input:focus-visible + label.e-btn,
+.e-btn-group ::deep input:checked + label.e-btn {
+ position: relative;
+ z-index: 1;
+}
+
/* Rounded corner (LTR) */
.e-btn-group.e-round-corner:not(.e-vertical):not(.e-rtl) {
border-radius: 20px;
@@ -986,4 +999,34 @@
background: var(--color-sf-danger-bg-color-hover) !important;
border-color: var(--color-sf-danger-border-color-hover) !important;
color: var(--color-sf-danger-text-hover) !important;
+}
+
+@media (forced-colors: active) {
+ .e-btn-group ::deep label.e-btn.e-primary.e-active,
+ .e-btn-group ::deep input:hover + label.e-btn.e-primary.e-active {
+ background: Highlight !important;
+ border-color: Highlight !important;
+ color: HighlightText !important;
+ forced-color-adjust: none;
+ }
+
+ .e-btn-group ::deep .e-btn:focus-visible,
+ .e-btn-group ::deep input:focus-visible + label.e-btn {
+ outline: 2px solid CanvasText !important;
+ outline-offset: 2px !important;
+ z-index: 1;
+ }
+ .e-btn-group ::deep .e-btn:hover,
+ .e-btn-group ::deep .e-btn:focus,
+ .e-btn-group ::deep .e-btn:focus-visible,
+ .e-btn-group ::deep input:hover + label.e-btn,
+ .e-btn-group ::deep input:focus + label.e-btn,
+ .e-btn-group ::deep input:focus-visible + label.e-btn {
+ position: relative;
+ z-index: 1;
+ }
+
+ .e-btn-group.e-vertical ::deep .e-btn:not(.e-outline) {
+ margin-top: -1px;
+ }
}
\ No newline at end of file
diff --git a/src/Components/Calendars/DatePicker/SfDatePicker.razor b/src/Components/Calendars/DatePicker/SfDatePicker.razor
index c582b30d..d090a792 100644
--- a/src/Components/Calendars/DatePicker/SfDatePicker.razor
+++ b/src/Components/Calendars/DatePicker/SfDatePicker.razor
@@ -18,7 +18,7 @@
@this.Placeholder
@if (this.ShowClearButton && !this.Readonly)
{
-
+
}
MouseIconHandlerAsync(args, DATEICONCLASS))">
@@ -29,7 +29,7 @@
@if (this.ShowClearButton && !this.Readonly)
{
-
+
}
MouseIconHandlerAsync(args, DATEICONCLASS))">
diff --git a/src/Components/Calendars/DatePicker/SfDatePicker.razor.css b/src/Components/Calendars/DatePicker/SfDatePicker.razor.css
index c1fda2cc..3d2d3003 100644
--- a/src/Components/Calendars/DatePicker/SfDatePicker.razor.css
+++ b/src/Components/Calendars/DatePicker/SfDatePicker.razor.css
@@ -2679,9 +2679,8 @@
.e-input-base-wrapper .e-float-input.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group) textarea:focus,
.e-input-base-wrapper .e-float-input:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group).e-input-focus input,
.e-input-base-wrapper .e-float-input.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group).e-input-focus input {
- border-bottom-color: var(--color-sf-primary);
padding-bottom: 2px;
- border-bottom-width: calc(var(--e-border) * 0.125);
+ border-bottom-width: 0px;
}
.e-input-base-wrapper textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error) {
border-bottom-color: var(--color-sf-primary);
@@ -5723,8 +5722,11 @@ ejs-calendar {
border-radius: calc(var(--e-radius) * 1);
}
.e-input-group-icon.e-timeline-today:focus,
-*.e-control-wrapper .e-input-group-icon.e-timeline-today:focus {
- background: var(--color-sf-secondary-text-color-hover);
+.e-input-group-icon.e-timeline-today:focus-visible,
+*.e-control-wrapper .e-input-group-icon.e-timeline-today:focus,
+*.e-control-wrapper .e-input-group-icon.e-timeline-today:focus-visible {
+ background: var(--color-sf-secondary-bg-color-hover);
+ color: var(--color-sf-secondary-text-color-hover);
border-radius: 999px;
}
diff --git a/src/Components/Calendars/DateTimePicker/SfDateTimePicker.razor.css b/src/Components/Calendars/DateTimePicker/SfDateTimePicker.razor.css
index 9cf53dce..06ce2b74 100644
--- a/src/Components/Calendars/DateTimePicker/SfDateTimePicker.razor.css
+++ b/src/Components/Calendars/DateTimePicker/SfDateTimePicker.razor.css
@@ -2636,9 +2636,8 @@
.e-input-base-wrapper .e-float-input.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group) textarea:focus,
.e-input-base-wrapper .e-float-input:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group).e-input-focus input,
.e-input-base-wrapper .e-float-input.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group).e-input-focus input {
- border-bottom-color: var(--color-sf-primary);
padding-bottom: 2px;
- border-bottom-width: calc(var(--e-border) * 0.125);
+ border-bottom-width: 0px;
}
.e-input-base-wrapper textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error) {
border-bottom-color: var(--color-sf-primary);
diff --git a/src/Components/Calendars/TimePicker/SfTimePicker.razor.css b/src/Components/Calendars/TimePicker/SfTimePicker.razor.css
index 01f4af28..e9d0aae3 100644
--- a/src/Components/Calendars/TimePicker/SfTimePicker.razor.css
+++ b/src/Components/Calendars/TimePicker/SfTimePicker.razor.css
@@ -2598,9 +2598,8 @@
.e-input-base-wrapper .e-float-input.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group) textarea:focus,
.e-input-base-wrapper .e-float-input:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group).e-input-focus input,
.e-input-base-wrapper .e-float-input.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error):not(.e-input-group).e-input-focus input {
- border-bottom-color: var(--color-sf-primary);
padding-bottom: 2px;
- border-bottom-width: calc(var(--e-border) * 0.125);
+ border-bottom-width: 0px;
}
.e-input-base-wrapper textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error) {
border-bottom-color: var(--color-sf-primary);
diff --git a/src/Components/Charts/Chart/SfChart.razor.LifeCycle.cs b/src/Components/Charts/Chart/SfChart.razor.LifeCycle.cs
index e21bf159..b6bcf3b6 100644
--- a/src/Components/Charts/Chart/SfChart.razor.LifeCycle.cs
+++ b/src/Components/Charts/Chart/SfChart.razor.LifeCycle.cs
@@ -517,6 +517,9 @@ await InvokeVoidAsync(_chartJsModule!, _chartJsInProcessModule!, Constants.SetTo
///
/// Disposes JavaScript module references used for interop, handling both asynchronous and in-process module instances in a safe and unified manner.
+ /// Safely disposes JS module references.
+ /// Ignores JSDisconnectedException when the Blazor circuit is already disconnected
+ /// (page refresh, tab close, navigation away). This does not affect chart UI behavior.
///
///
/// The asynchronous JavaScript module reference ( ) to dispose.
@@ -524,13 +527,38 @@ await InvokeVoidAsync(_chartJsModule!, _chartJsInProcessModule!, Constants.SetTo
///
/// The in-process JavaScript module reference ( ) to dispose.
///
- private static async ValueTask DisposeJsModuleAsync(IJSObjectReference? asyncModule, IJSInProcessObjectReference? inProcessModule)
+ private static async ValueTask DisposeJsModuleAsync(
+ IJSObjectReference? asyncModule,
+ IJSInProcessObjectReference? inProcessModule)
{
- if (asyncModule is not null)
+ try
+ {
+ if (asyncModule is not null)
+ {
+ await asyncModule.DisposeAsync().ConfigureAwait(true);
+ }
+ }
+ catch (JSDisconnectedException)
+ {
+ // Expected on circuit teardown. Browser context is already gone.
+ }
+ catch (ObjectDisposedException)
+ {
+ // Module/runtime already disposed.
+ }
+ catch (TaskCanceledException)
+ {
+ // Disposal cancelled during circuit shutdown.
+ }
+
+ try
+ {
+ inProcessModule?.Dispose();
+ }
+ catch (ObjectDisposedException)
{
- await asyncModule.DisposeAsync().ConfigureAwait(true);
+ // Ignore.
}
- inProcessModule?.Dispose();
}
#endregion
diff --git a/src/Components/Inputs/CheckBox/SfCheckBox.razor.css b/src/Components/Inputs/CheckBox/SfCheckBox.razor.css
index ba906c12..aebee0e2 100644
--- a/src/Components/Inputs/CheckBox/SfCheckBox.razor.css
+++ b/src/Components/Inputs/CheckBox/SfCheckBox.razor.css
@@ -165,27 +165,27 @@
color: var(--color-sf-content-text-color);
}
-.e-checkbox-wrapper .e-checkbox:focus + .e-frame:not(.e-check):not(.e-stop),
-.e-checkbox-wrapper .e-checkbox:active + .e-frame:not(.e-check):not(.e-stop),
-.e-css.e-checkbox-wrapper .e-checkbox:focus + .e-frame:not(.e-check):not(.e-stop),
-.e-css.e-checkbox-wrapper .e-checkbox:active + .e-frame:not(.e-check):not(.e-stop) {
+.e-checkbox-wrapper .e-checkbox:focus ~ .e-frame:not(.e-check):not(.e-stop),
+.e-checkbox-wrapper .e-checkbox:active ~ .e-frame:not(.e-check):not(.e-stop),
+.e-css.e-checkbox-wrapper .e-checkbox:focus ~ .e-frame:not(.e-check):not(.e-stop),
+.e-css.e-checkbox-wrapper .e-checkbox:active ~ .e-frame:not(.e-check):not(.e-stop) {
background-color: var(--color-sf-content-bg-color);
border-color: var(--color-sf-border-alt);
box-shadow: none;
}
-.e-checkbox-wrapper .e-checkbox:focus + .e-frame.e-stop, .e-checkbox-wrapper .e-checkbox:active + .e-frame.e-stop,
-.e-css.e-checkbox-wrapper .e-checkbox:focus + .e-frame.e-stop,
-.e-css.e-checkbox-wrapper .e-checkbox:active + .e-frame.e-stop {
+.e-checkbox-wrapper .e-checkbox:focus ~ .e-frame.e-stop, .e-checkbox-wrapper .e-checkbox:active ~ .e-frame.e-stop,
+.e-css.e-checkbox-wrapper .e-checkbox:focus ~ .e-frame.e-stop,
+.e-css.e-checkbox-wrapper .e-checkbox:active ~ .e-frame.e-stop {
background-color: var(--color-sf-content-bg-color);
border-color: var(--color-sf-primary);
box-shadow: none;
color: var(--color-sf-primary-bg-color-hover);
}
-.e-checkbox-wrapper .e-checkbox:focus + .e-frame.e-check, .e-checkbox-wrapper .e-checkbox:active + .e-frame.e-check,
-.e-css.e-checkbox-wrapper .e-checkbox:focus + .e-frame.e-check,
-.e-css.e-checkbox-wrapper .e-checkbox:active + .e-frame.e-check {
+.e-checkbox-wrapper .e-checkbox:focus ~ .e-frame.e-check, .e-checkbox-wrapper .e-checkbox:active ~ .e-frame.e-check,
+.e-css.e-checkbox-wrapper .e-checkbox:focus ~ .e-frame.e-check,
+.e-css.e-checkbox-wrapper .e-checkbox:active ~ .e-frame.e-check {
box-shadow: none;
}
diff --git a/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor b/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor
index 59709012..7a9d3929 100644
--- a/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor
+++ b/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor
@@ -30,11 +30,15 @@
+ @onmouseup:preventDefault="@IsSpinButtonChanged"
+ @onclick="@(() => OnSpinButtonActivateAsync("decrement"))"
+ @onkeydown="@((KeyboardEventArgs e) => OnSpinButtonKeyDownAsync(e, "decrement"))">
+ @onmouseup:preventDefault="@IsSpinButtonChanged"
+ @onclick="@(() => OnSpinButtonActivateAsync("increment"))"
+ @onkeydown="@((KeyboardEventArgs e) => OnSpinButtonKeyDownAsync(e, "increment"))">
}
}
@@ -57,11 +61,15 @@
+ @onmouseup:preventDefault="@IsSpinButtonChanged"
+ @onclick="@(() => OnSpinButtonActivateAsync("decrement"))"
+ @onkeydown="@((KeyboardEventArgs e) => OnSpinButtonKeyDownAsync(e, "decrement"))">
+ @onmouseup:preventDefault="@IsSpinButtonChanged"
+ @onclick="@(() => OnSpinButtonActivateAsync("increment"))"
+ @onkeydown="@((KeyboardEventArgs e) => OnSpinButtonKeyDownAsync(e, "increment"))">
}
}
diff --git a/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor.cs b/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor.cs
index 43ed10a6..23c9dc76 100644
--- a/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor.cs
+++ b/src/Components/Inputs/NumericTextBox/SfNumericTextBox.razor.cs
@@ -1433,6 +1433,50 @@ private async Task MouseUpOnSpinnerAsync()
}
}
+ ///
+ /// Activates a spin button (increment or decrement) when the user clicks it or activates it with the keyboard.
+ ///
+ ///
+ /// The action to perform. Use "increment" to increase the value or "decrement" to decrease the value.
+ ///
+ /// A that represents the asynchronous operation.
+ ///
+ /// This method is shared by both mouse click and keyboard (Enter / Space) activation of the spin buttons.
+ /// It reuses the existing path so that value changes, validation,
+ /// formatting, and change events behave consistently with the mouse and arrow-key paths.
+ /// The method does nothing when the component is in a read-only or disabled state.
+ ///
+ private async Task OnSpinButtonActivateAsync(string action)
+ {
+ if (Readonly || Disabled)
+ {
+ return;
+ }
+
+ string currentValue = Value is not null ? Value.ToString()! : string.Empty;
+ await ServerActionAsync(action, null, currentValue).ConfigureAwait(true);
+ }
+
+ ///
+ /// Handles keyboard activation of the spin buttons.
+ ///
+ /// The that contains information about the key that was pressed.
+ ///
+ /// The action to perform. Use "increment" for the up button or "decrement" for the down button.
+ ///
+ /// A that represents the asynchronous operation.
+ ///
+ /// Responds to the Enter and Space keys so that focusable spin buttons are fully operable
+ /// with the keyboard, matching the behavior of a standard button control.
+ ///
+ private async Task OnSpinButtonKeyDownAsync(KeyboardEventArgs e, string action)
+ {
+ if (e.Key is "Enter" or " ")
+ {
+ await OnSpinButtonActivateAsync(action).ConfigureAwait(true);
+ }
+ }
+
///
/// Performs increment or decrement actions on the numeric value using the specified step value.
///
diff --git a/src/Components/Inputs/TextArea/SfTextArea.razor.css b/src/Components/Inputs/TextArea/SfTextArea.razor.css
index 765b4652..fb833c2f 100644
--- a/src/Components/Inputs/TextArea/SfTextArea.razor.css
+++ b/src/Components/Inputs/TextArea/SfTextArea.razor.css
@@ -264,9 +264,8 @@
}
.e-input-base-wrapper .e-input:focus {
border-width: 1px;
- padding-bottom: 4px;
}
-.e-input-base-wrapper .e-input:focus {
+.e-input-base-wrapper input.e-input:focus {
padding-bottom: 5px;
}
.e-input-base-wrapper .e-input-group input.e-input:focus,
@@ -277,8 +276,8 @@
.e-input-base-wrapper .e-input-group.e-control-wrapper.e-input-focus input.e-input {
padding: 4px 12px 4px 12px;
}
-.e-input-base-wrapper .e-input-group textarea.e-input:focus,
-.e-input-base-wrapper .e-input-group.e-control-wrapper textarea.e-input:focus {
+.e-input-base-wrapper .e-input-group:not(.e-filled) textarea.e-input:focus,
+.e-input-base-wrapper .e-input-group.e-control-wrapper:not(.e-filled) textarea.e-input:focus {
padding: 4px 8px 4px 12px;
}
.e-input-base-wrapper .e-input-group .e-input-group-icon,
@@ -2496,9 +2495,13 @@
}
.e-input-base-wrapper textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error) {
border-bottom-color: var(--color-sf-primary);
- padding-bottom: 4px;
border-bottom-width: calc(var(--e-border) * 0);
}
+.e-input-base-wrapper :not(.e-filled) > textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error),
+.e-input-base-wrapper .e-input-group:not(.e-filled) textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error),
+.e-input-base-wrapper .e-input-group.e-control-wrapper:not(.e-filled) textarea.e-input:focus:not(.e-success):not(.e-warning):not(.e-error) {
+ padding-bottom: 4px;
+}
.e-input-base-wrapper .e-input-group:not(.e-success):not(.e-warning):not(.e-error) input.e-input:focus,
.e-input-base-wrapper .e-input-group.e-control-wrapper:not(.e-success):not(.e-warning):not(.e-error) input.e-input:focus,
.e-input-base-wrapper .e-input-group:not(.e-success):not(.e-warning):not(.e-error).e-input-focus input.e-input,
diff --git a/src/Components/Inputs/TextBox/SfTextBox.razor.css b/src/Components/Inputs/TextBox/SfTextBox.razor.css
index ef61b466..917657f5 100644
--- a/src/Components/Inputs/TextBox/SfTextBox.razor.css
+++ b/src/Components/Inputs/TextBox/SfTextBox.razor.css
@@ -3533,4 +3533,11 @@ span.e-input-group.e-append-wrapper.e-rtl.e-multi-line-input.e-outline textarea.
padding-right: 1px;
}
+.e-input-base-wrapper .e-input-group.e-float-input.e-prepend > .e-input-group-icon,
+.e-input-base-wrapper .e-input-group.e-control-wrapper.e-float-input.e-prepend > .e-input-group-icon,
+.e-input-base-wrapper .e-input-group.e-float-input.e-append > .e-input-group-icon,
+.e-input-base-wrapper .e-input-group.e-control-wrapper.e-float-input.e-append > .e-input-group-icon {
+ padding-bottom: 7px;
+}
+
/* stylelint-disable-line no-empty-source */
diff --git a/src/Components/Popups/Tooltip/SfTooltip.razor.css b/src/Components/Popups/Tooltip/SfTooltip.razor.css
index a03538ff..08e3f28a 100644
--- a/src/Components/Popups/Tooltip/SfTooltip.razor.css
+++ b/src/Components/Popups/Tooltip/SfTooltip.razor.css
@@ -292,4 +292,22 @@
::deep .e-tooltip-wrap .e-arrow-tip-outer,
::deep .e-tooltip-wrap .e-arrow-tip-inner {
display: block !important;
+}
+
+@media (forced-colors: active) {
+
+ .e-tooltip-wrap .e-arrow-tip,
+ ::deep .e-tooltip-wrap .e-arrow-tip {
+ border: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+ background: transparent !important;
+ }
+
+ .e-tooltip-wrap .e-arrow-tip-outer,
+ .e-tooltip-wrap .e-arrow-tip-inner,
+ ::deep .e-tooltip-wrap .e-arrow-tip-outer,
+ ::deep .e-tooltip-wrap .e-arrow-tip-inner {
+ forced-color-adjust: none;
+ }
}
\ No newline at end of file
diff --git a/src/Syncfusion.Blazor.Toolkit.csproj b/src/Syncfusion.Blazor.Toolkit.csproj
index 59387a58..3836fedd 100644
--- a/src/Syncfusion.Blazor.Toolkit.csproj
+++ b/src/Syncfusion.Blazor.Toolkit.csproj
@@ -60,27 +60,7 @@
true
moderate
all
-
- CA2100;CA2102;CA2103;CA2108;CA2115;CA2116;CA2117;CA2118;CA2134;CA2136;CA2143;CA2144;CA2146;CA2147;CA2149;CA2150;CA2151;CA2152;CA2153;CA2154;CA2155;CA2156;CA2157;CA2158;CA2159;CA2300;CA2301;CA2302;CA2303;CA2304;CA2305;CA2306;CA2307;CA2308;CA2309;CA2310;CA2311;CA2312;CA2313;CA2314;CA2315;CA2316;CA2317;CA2318;CA2319;CA2320;CA2321;CA2322;CA2323;CA2324;CA2325;CA2326;CA2327;CA2328;CA2329;CA2330;CA2331;CA2332;CA2333;CA2334;CA2335;CA2336;CA2337;CA2338;CA2339;CA2340;CA2341;CA2342;CA2343;CA2344;CA2345;CA2346;CA2347;CA2348;CA2349;CA2350;CA2351;CA2352;CA2353;CA2354;CA2355;CA2356;CA2357;CA2358;CA2359;CA2360;CA2361;CA2362;CA2363;CA2364;CA2365;CA2366;CA2367;CA2368;CA2369;CA2370;CA2371;CA2372;CA2373;CA2374;CA2375;CA2376;CA2377;CA2378;CA2379;CA2380;CA2381;CA2382;CA2383;CA2384;CA2385;CA2386;CA2387;CA2388;CA2389;CA2390;CA2391;CA2392;CA2393;CA2394;CA2395;CA2396;CA2397;CA2398;CA2399;CA3075;CA3076;CA3077;CA5350;CA5351;CA5352;CA5353;CA5354;CA5355;CA5356;CA5357;CA5358;CA5359;CA5360;CA5361;CA5362;CA5363;CA5364;CA5365;CA5366;CA5367;CA5368;CA5369;CA5370;CA5371;CA5372;CA5373;CA5374;CA5375;CA5376;CA5377;CA5378;CA5379;CA5380;CA5381;CA5382;CA5383;CA5384;CA5385;CA5386;CA5387;CA5388;CA5389;CA5390;CA5391;CA5392;CA5393;CA5394;CA5395;CA5396;CA5397;CA5398;CA5399;CA5400;CA5401;CA5402;CA5403;CA5404;CA5405
+ true
diff --git a/src/wwwroot/scripts/uploader.js b/src/wwwroot/scripts/uploader.js
index 301a9713..fdbe438d 100644
--- a/src/wwwroot/scripts/uploader.js
+++ b/src/wwwroot/scripts/uploader.js
@@ -4,7 +4,7 @@ var globalTimeOut = {};
var DEFAULT_FLUENT_WIDTH = 30;
var CLS_SHOWSPIN = 'e-spin-show';
var CLS_HIDESPIN = 'e-spin-hide';
-var CLS_FLUENTSPIN = 'e-spin-fabric';
+var CLS_FLUENTSPIN = 'e-spin-fluent';
var CLS_SPINWRAP = 'e-spinner-pane';
var CLS_SPINNER_INNER_WRAP = 'e-spinner-inner';
var CLS_SPINCIRCLE = 'e-path-circle';
@@ -172,10 +172,21 @@ function createFluentElement(innerCon, uniqueID, themeClass, makeElement) {
var svgFabric = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgFabric.setAttribute('id', uniqueID);
svgFabric.setAttribute('class', themeClass);
+
var fabricCirclePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
fabricCirclePath.setAttribute('class', CLS_SPINCIRCLE);
+ // Prevent default black fill — track only
+ fabricCirclePath.setAttribute('fill', 'none');
+ fabricCirclePath.setAttribute('stroke', 'var(--color-sf-primary-lighter, #c7e0f4)');
+ fabricCirclePath.setAttribute('stroke-width', '1.5');
+
var fabricCircleArc = document.createElementNS('http://www.w3.org/2000/svg', 'path');
fabricCircleArc.setAttribute('class', CLS_SPINARC);
+ // Prevent default black fill — animated arc
+ fabricCircleArc.setAttribute('fill', 'none');
+ fabricCircleArc.setAttribute('stroke', 'var(--color-sf-primary, #0078d4)');
+ fabricCircleArc.setAttribute('stroke-width', '1.5');
+
innerCon.insertBefore(svgFabric, innerCon.firstChild);
svgFabric.appendChild(fabricCirclePath);
svgFabric.appendChild(fabricCircleArc);