- @for (int i = 0; i < _items.Count; i++)
+ @if (Options is not null || ChildContent is not null)
{
- var item = _items[i];
-
- var icon = GetIcon(item);
- var hideDot = GetHideDot(item);
- var iconName = GetIconName(item);
- var dotTemplate = GetDotTemplate(item);
- var primaryContent = GetPrimaryContent(item);
- var isEnabled = IsEnabled && GetIsEnabled(item);
- var secondaryContent = GetSecondaryContent(item);
-
- icon = BitIconInfo.From(icon, iconName);
-
-
HandleOnItemClick(item)"
- tabindex="@(isEnabled ? 0 : -1)"
- disabled="@(isEnabled is false)"
- aria-disabled="@(isEnabled is false)"
- style="@GetStyle(item) @Styles?.Item"
- class="bit-tln-itm@(GetItemClasses(item)) @Classes?.Item">
-
- @if (primaryContent is not null)
- {
- @primaryContent(item)
- }
- else
- {
- @GetPrimaryText(item)
- }
-
-
- @if (hideDot is false)
- {
- @if (dotTemplate is not null)
- {
- @dotTemplate(item)
- }
- else
- {
-
- @if (icon is not null)
- {
-
- }
-
- }
- }
-
-
- @if (secondaryContent is not null)
- {
- @secondaryContent(item)
- }
- else
- {
- @GetSecondaryText(item)
- }
-
-
+
+ @(Options ?? ChildContent)
+
+ }
+ else
+ {
+ @for (int i = 0; i < _items.Count; i++)
+ {
+ var item = _items[i];
+ <_BitTimelineItem Timeline="this" Item="item" @key="@GetItemKey(item, i.ToString())" />
+ }
}
\ No newline at end of file
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.razor.cs
index ff759181b9..3efe5ef085 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Lists/Timeline/BitTimeline.razor.cs
@@ -152,18 +152,69 @@ protected override void RegisterCssStyles()
protected override Task OnParametersSetAsync()
{
- if (ChildContent is null && Items.Any() && Items != _oldItems)
+ // Note: no Items.Any() guard here, so a transition from a populated collection to an empty one
+ // still runs the comparison and clears _items instead of leaving the previous items rendered.
+ if (ChildContent is null && Options is null &&
+ (_oldItems is null || Items.SequenceEqual(_oldItems) is false))
{
- _oldItems = Items;
_items = Items.ToList();
+ // Store an independent snapshot so a later in-place mutation of the same Items instance is
+ // still detected (a reference copy would compare the collection against itself).
+ _oldItems = _items.ToList();
}
+ // Options render their items themselves and Blazor skips re-rendering them when only the
+ // timeline's own parameters (Styles, IsEnabled, ...) change, so push a re-render to each one.
+ RefreshOptions();
+
return base.OnParametersSetAsync();
}
+ private void RefreshOptions()
+ {
+ // In the Items API there are no registered options, so there is nothing to refresh.
+ if ((Options ?? ChildContent) is null) return;
+
+ foreach (var item in _items)
+ {
+ (item as BitTimelineOption)?.InternalStateHasChanged();
+ }
+ }
+
+ // Honors the item's own Key when provided (stable identity across reordering) and otherwise falls
+ // back to a per-component, position-based key so that duplicate/equal items cannot collide.
+ private string GetItemKey(TItem item, string defaultKey)
+ {
+ return GetKey(item) ?? $"{UniqueId}-{defaultKey}";
+ }
+
+ private string? GetKey(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitTimelineItem timelineItem)
+ {
+ return timelineItem.Key;
+ }
+
+ if (item is BitTimelineOption timelineOption)
+ {
+ return timelineOption.Key;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.Key.Selector is not null)
+ {
+ return NameSelectors.Key.Selector!(item);
+ }
+
+ return item.GetValueFromProperty