Add BitFullCalendar extras component (#12449)#12450
Conversation
WalkthroughThis PR adds a new ChangesBitFullCalendar end-to-end feature
Sequence Diagram(s)sequenceDiagram
participant User
participant BitFullCalendar
participant BitFullCalendarState
participant BitBlazorUI.FullCalendar
User->>BitFullCalendar: click day/hour/resource slot
BitFullCalendar->>BitFullCalendarState: create or update draft/event state
User->>BitBlazorUI.FullCalendar: drag resize handle
BitBlazorUI.FullCalendar->>BitFullCalendar: OnResizeStart / OnResizeMove / OnResizeEnd
BitFullCalendar->>BitFullCalendarState: UpdateEvent(...)
BitFullCalendarState-->>BitFullCalendar: OnStateChanged / OnDateRangeChanged
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟡 Minor comments (11)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/FullCalendar/BitFullCalendarDemo.razor-5-5 (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPolish the page description text for user-facing docs.
Current wording is awkward and inconsistent in casing (
"fullcalendar component of the bit BlazorUI components"). Consider a clearer description, e.g. “FullCalendar component for Bit BlazorUI.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/FullCalendar/BitFullCalendarDemo.razor` at line 5, The Description attribute text in the BitFullCalendarDemo.razor component has awkward wording and inconsistent casing. Update the Description property to use proper casing for "FullCalendar" (currently lowercase) and simplify the text to remove redundancy. Instead of "fullcalendar component of the bit BlazorUI components", use clearer phrasing such as "FullCalendar component for Bit BlazorUI" to improve readability and consistency for user-facing documentation.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarHelpers.cs-213-218 (1)
213-218:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse active culture for time labels instead of
InvariantCulture.At Line 216 and Line 253, forcing invariant formatting breaks localization consistency for time strings in non-English cultures.
Suggested fix
- public static string FormatTime(DateTime date, bool use24Hour) + public static string FormatTime(DateTime date, bool use24Hour, CultureInfo? culture = null) { + culture ??= CultureInfo.CurrentUICulture; return use24Hour - ? date.ToString("HH:mm", CultureInfo.InvariantCulture) - : date.ToString("h:mm tt", CultureInfo.InvariantCulture); + ? date.ToString("HH:mm", culture) + : date.ToString("h:mm tt", culture); } @@ - public static string FormatHourLabel(int hour, bool use24Hour) + public static string FormatHourLabel(int hour, bool use24Hour, CultureInfo? culture = null) { var dt = DateTime.Today.AddHours(hour); + culture ??= CultureInfo.CurrentUICulture; return use24Hour - ? dt.ToString("HH:00", CultureInfo.InvariantCulture) - : dt.ToString("h tt", CultureInfo.InvariantCulture); + ? dt.ToString("HH:00", culture) + : dt.ToString("h tt", culture); }Also applies to: 248-254
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarHelpers.cs` around lines 213 - 218, The FormatTime method is using CultureInfo.InvariantCulture for both the 24-hour and 12-hour time format calls, which prevents localization for non-English cultures. Replace CultureInfo.InvariantCulture with CultureInfo.CurrentCulture in both date.ToString() calls within the FormatTime method to respect the user's active culture settings. Apply the same fix to the similar time formatting code mentioned in the "Also applies to" section around lines 248-254.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarEvent.cs-25-27 (1)
25-27:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTreat midnight
EndDateas exclusive for day-span classification.At Line 25,
IsSingleDayuses directDateequality, so an event like2026-06-20 22:00 -> 2026-06-21 00:00is flagged as multi-day even though it ends at the day boundary.Suggested fix
public class BitFullCalendarEvent { @@ - public bool IsSingleDay => StartDate.Date == EndDate.Date; + private DateTime EndDateForDaySpan => + EndDate.TimeOfDay == TimeSpan.Zero && EndDate > StartDate + ? EndDate.AddTicks(-1) + : EndDate; + + public bool IsSingleDay => StartDate.Date == EndDateForDaySpan.Date; public bool IsMultiDay => !IsSingleDay; public TimeSpan Duration => EndDate - StartDate;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarEvent.cs` around lines 25 - 27, The IsSingleDay property at line 25 of BitFullCalendarEvent class does not treat midnight EndDate as exclusive, causing events ending at 00:00 of the next day to be incorrectly classified as multi-day. Fix this by adjusting the Date comparison in the IsSingleDay property to subtract one tick or unit of time from EndDate before comparing dates, ensuring that an EndDate at midnight is treated as the end of the previous day rather than the start of the next day.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarChangeEventArgs.cs-49-49 (1)
49-49:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMake
Sourcerequired to prevent silent origin misclassification.If omitted,
Sourcedefaults toDialog(enum zero), which can report incorrect origin metadata in emitted change payloads. Mark itrequiredto force explicit assignment.Proposed fix
- public BitFullCalendarChangeSource Source { get; init; } + public required BitFullCalendarChangeSource Source { get; init; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarChangeEventArgs.cs` at line 49, Add the `required` keyword to the Source property in the BitFullCalendarChangeEventArgs class to force callers to explicitly assign a BitFullCalendarChangeSource value, preventing silent defaults to Dialog (the zero enum value) which can cause incorrect origin metadata in change payloads.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcCalendarHeader.razor-20-23 (1)
20-23:⚠️ Potential issue | 🟡 MinorSet add-event button type explicitly to avoid accidental form submission.
Without
type="button", this button defaults totype="submit"per HTML specification. If the FullCalendar component is used inside a form context, the button will unintentionally trigger form submission instead of only calling theOnAddEventClickhandler. Adding the explicit type prevents this risk.🛠️ Suggested fix
- <button class="bit-bfc-btn bit-bfc-btn-primary bit-bfc-btn-sm" `@onclick`="OnAddEventClick"> + <button type="button" class="bit-bfc-btn bit-bfc-btn-primary bit-bfc-btn-sm" `@onclick`="OnAddEventClick">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcCalendarHeader.razor` around lines 20 - 23, The button element with the OnAddEventClick handler in BitFcCalendarHeader.razor lacks an explicit type attribute, causing it to default to type="submit" per HTML specification. This can trigger unintended form submission if the FullCalendar component is placed within a form context. Add type="button" explicitly to the button element that contains the OnAddEventClick event handler to ensure it only calls the click handler and does not submit any parent form.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventListDialog.razor-6-6 (1)
6-6:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse calendar culture for the dialog date header.
This currently formats with default culture instead of
State.Culture, causing localization inconsistencies.Suggested fix
- <h3>`@Texts.EventListTitlePrefix` `@Date.ToString`("dddd, MMM d, yyyy")</h3> + <h3>`@Texts.EventListTitlePrefix` `@Date.ToString`("dddd, MMM d, yyyy", State.Culture)</h3>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventListDialog.razor` at line 6, In the BitFcEventListDialog.razor component's h3 element, the date formatting using Date.ToString("dddd, MMM d, yyyy") does not respect the calendar's culture setting. Modify the Date.ToString call to include State.Culture as a culture parameter so that the date is formatted according to the calendar's culture instead of the default culture. This will ensure the date header displays in the correct localized format.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventDetailsDialog.razor.cs-19-23 (1)
19-23:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAwait
OnClose.InvokeAsync()in the edit-close flow.The callback task is currently not awaited.
Suggested fix
- private void OnEditClose() + private async Task OnEditClose() { _showEdit = false; - OnClose.InvokeAsync(); + await OnClose.InvokeAsync(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventDetailsDialog.razor.cs` around lines 19 - 23, The OnEditClose() method invokes the OnClose callback asynchronously but does not await the result, which can cause the callback to not complete properly. Change the OnEditClose() method signature from void to async Task, and add the await keyword before the OnClose.InvokeAsync() call to ensure the asynchronous operation completes before the method returns.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarWeekView.razor.cs-36-49 (1)
36-49:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winKeep
SelectedDateconsistent across both add-event paths.
State.SetSelectedDate(day)is only applied whenOnAddClickis delegated, not when using the built-in add dialog.Suggested fix
private async Task OnHourClickAsync(DateTime day, int hour) { + State.SetSelectedDate(day); + if (OnAddClick.HasDelegate) { - State.SetSelectedDate(day); var draft = BitFullCalendarHelpers.CreateDraftEventForTimeSlot(day, hour); await OnAddClick.InvokeAsync(draft); return; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarWeekView.razor.cs` around lines 36 - 49, The OnHourClickAsync method only calls State.SetSelectedDate(day) when OnAddClick has a delegate, but not when using the built-in add dialog (when _addDate, _addHour, and _showAddDialog are set). Move the State.SetSelectedDate(day) call outside of the if block so it executes regardless of which path is taken, ensuring the SelectedDate remains consistent across both the delegated callback path and the built-in dialog path.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/YearView/BitFcCalendarYearView.razor-8-10 (1)
8-10:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse calendar month count instead of hardcoded 12.
Line 8 should use the active calendar’s month count (
GetMonthsInYear) to avoid incorrect year rendering in calendars that don’t match a fixed 12-month assumption.Proposed fix
- var months = Enumerable.Range(1, 12) + var monthsInYear = cal.GetMonthsInYear(culturalYear); + var months = Enumerable.Range(1, monthsInYear) .Select(m => cal.ToDateTime(culturalYear, m, 1, 0, 0, 0, 0)) .ToArray();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/YearView/BitFcCalendarYearView.razor` around lines 8 - 10, Replace the hardcoded value 12 in the Enumerable.Range call within the months variable assignment with a dynamic call to the calendar's month count method. Instead of Range(1, 12), use Range(1, cal.GetMonthsInYear(culturalYear)) or the appropriate equivalent method on the cal object to retrieve the actual number of months in the given year, ensuring the code correctly handles calendars with varying month counts.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineMonthView.razor-79-80 (1)
79-80:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDrop preview can stick after drag cancellation.
Preview class application should be tied to active drag state; otherwise stale highlights can remain after drag ends outside a drop target.
Suggested fix
- var isPreview = _dragResourceId == rowKey && _dragDay == day.Date; + var isPreview = State.IsDragging && _dragResourceId == rowKey && _dragDay == day.Date; ... - var isPreview = _dragResourceId == rowKey && _dragDay == day.Date; + var isPreview = State.IsDragging && _dragResourceId == rowKey && _dragDay == day.Date;Also applies to: 106-107
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineMonthView.razor` around lines 79 - 80, The isPreview condition on lines 79-80 (and also on lines 106-107) only checks if the current cell matches the drag source cell, but does not verify if an active drag operation is in progress. When a drag is cancelled, these variables retain their values, causing stale preview highlights to remain. Add a check for an active drag state (such as a boolean flag tracking whether a drag is currently in progress) to the isPreview condition so that the preview class is only applied when there is both an active drag operation and the cell matches the drag source.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineDayView.razor-60-61 (1)
60-61:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDrop preview can remain visible after drag cancel/end.
Preview styling is computed without checking active drag state, so a canceled drag can leave stale highlighted cells.
Suggested fix
- var isPreviewHour = _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 0; - var isPreviewHalf = _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 30; + var isPreviewHour = State.IsDragging && _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 0; + var isPreviewHalf = State.IsDragging && _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 30; ... - var isPreviewHour = _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 0; - var isPreviewHalf = _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 30; + var isPreviewHour = State.IsDragging && _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 0; + var isPreviewHalf = State.IsDragging && _dragResourceId == rowKey && _dragHour == hour && _dragMinute == 30;Also applies to: 95-96
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineDayView.razor` around lines 60 - 61, The preview styling for drag operations in BitFcTimelineDayView.razor is computed without verifying that an active drag is in progress, allowing stale preview highlighting to persist after a drag is canceled. Add a check to verify that a drag operation is currently active before setting isPreviewHour and isPreviewHalf variables around lines 60-61. Additionally, apply the same fix to the similar preview checks mentioned at lines 95-96. The condition should verify that _dragResourceId is not null or empty and that an active drag state exists before allowing the preview styles to be applied.
🧹 Nitpick comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarColorScheme.cs (1)
90-101: 🏗️ Heavy liftUse deterministic lexical fallback for unknown color IDs.
Line 101 uses a hash-based fallback, which does not implement “unknown ids by name” and can reorder groups between process runs. Prefer a stable secondary textual key at sort callsites (e.g.,
ThenBy(colorId, OrdinalIgnoreCase)).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarColorScheme.cs` around lines 90 - 101, The GetSortOrder method in BitFullCalendarColorScheme currently uses StringComparer.OrdinalIgnoreCase.GetHashCode() for unknown color IDs, which is non-deterministic across process runs and doesn't implement text-based sorting as documented. Replace the hash-based fallback on line 101 with a deterministic approach that sorts unknown IDs lexically by their string value instead of relying on hash codes, such as computing a value based on the ordinal character values of the trimmed string or implementing a stable text-based comparison alternative.src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor (1)
11-16: ⚡ Quick winPre-index week events to avoid repeated linear scans in render.
At Line 14,
FirstOrDefaultover_weekEventsruns inside nested day/row loops, which makes rerenders grow quadratically with event volume. Precompute a(day,row) -> eventlookup inOnParametersSetand render with O(1) lookups.♻️ Proposed optimization
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor.cs @@ - private Dictionary<string, int> _eventRows = new(); + private Dictionary<string, int> _eventRows = new(); + private Dictionary<(DateTime Day, int Row), BitFullCalendarEvent> _weekEventsByDayRow = new(); @@ - _eventRows = new Dictionary<string, int>(); + _eventRows = new Dictionary<string, int>(); + _weekEventsByDayRow = new(); @@ - foreach (var d in evDays) - rowUsageByDay[d.Date].Add(row); + foreach (var d in evDays) + { + var day = d.Date; + rowUsageByDay[day].Add(row); + _weekEventsByDayRow[(day, row)] = ev; + } break; } } }--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor @@ - var ev = _weekEvents.FirstOrDefault(e => - _eventRows.GetValueOrDefault(e.Id) == r && - e.StartDate.Date <= d.Date && e.EndDate.Date >= d.Date); + _weekEventsByDayRow.TryGetValue((d.Date, r), out var ev);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor` around lines 11 - 16, The FirstOrDefault call over _weekEvents is executed inside nested loops for each day and row, causing quadratic time complexity during rerenders. Create a precomputed lookup dictionary in the OnParametersSet lifecycle method that maps (date, row) tuples to events based on the same filtering criteria (checking _eventRows values and date ranges with StartDate.Date and EndDate.Date comparisons). Then replace the FirstOrDefault logic in the render loop with a simple O(1) dictionary lookup using the current day and row as the key.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarToast.razor.cs`:
- Around line 8-21: The RemoveAfterDelay method mutates _toasts without
dispatcher protection on line 19, creating a race condition with the Razor
template's foreach loop. Additionally, delayed removal tasks are never canceled
when the component is disposed. To fix this, wrap the _toasts.RemoveAll() call
inside RemoveAfterDelay with InvokeAsync() to ensure it runs on the renderer
dispatcher. Next, create a collection to track CancellationTokenSource objects
for each delayed removal task, modify RemoveAfterDelay to accept a
CancellationToken and check it after the Task.Delay, and implement
IAsyncDisposable on the component class to cancel all pending removal tasks when
the component is disposed.
---
Major comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.razor.cs`:
- Around line 156-159: The ResolveCulture() method in BitFullCalendar.razor.cs
attempts to instantiate CultureInfo with a user-provided name without handling
potential exceptions. Wrap the CultureInfo(name) constructor call in a try-catch
block that catches CultureNotFoundException and falls back to the default
culture (Culture ?? CultureInfo.CurrentUICulture) when an invalid culture name
is provided. This prevents the component initialization from failing due to
invalid CultureName parameter values.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.scss`:
- Line 3: Remove the `.scss` file extension from the import statement in the
BitFullCalendar.scss file. The import path for the functions partial currently
includes `functions.scss` but should be changed to `functions` (without the
extension) to comply with the scss/load-partial-extension linting rule. Update
the import statement to remove the `.scss` extension while keeping the relative
path and quotes intact.
- Line 197: The keyframe names bfcFadeIn and bfcSlideUp are using camelCase
naming, which violates the stylelint keyframes-name-pattern rule that requires
kebab-case. Rename bfcFadeIn to bfc-fade-in and bfcSlideUp to bfc-slide-up in
both their keyframe definitions (where they are declared with `@keyframes`) and in
all animation property references throughout the BitFullCalendar.scss file.
Ensure all 8 occurrences are updated consistently to use the kebab-case format.
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.ts`:
- Around line 113-134: In the endResize function, the OnResizeEnd call must
always execute even if the OnResizeMove invocation fails. Wrap the await
dotNetRef.invokeMethodAsync call for OnResizeMove in a try-finally block, and
move the await dotNetRef.invokeMethodAsync call for OnResizeEnd into the finally
block to guarantee it always executes regardless of whether the OnResizeMove
call succeeds or throws an error. Apply the same fix to the corresponding
endResize function in the other section mentioned at lines 183-204.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcAddEditEventDialog.razor`:
- Around line 16-17: Labels in the BitFcAddEditEventDialog component are not
programmatically associated with their corresponding input controls, which
breaks accessibility for screen readers. Add a unique id attribute to each input
element (the text inputs near the TitleLabel, and other form fields at lines
25-26, 30-31, 39-40, 49-50, and 76-79), then add a for attribute to each label
element that matches the id of its paired input. For example, add
id="_titleInput" to the input with `@bind`="_title" and add for="_titleInput" to
the label for TitleLabel.
- Around line 68-70: The attendee remove button at line 68 is an icon-only
button without an accessible label, which makes it unclear to assistive
technologies. Add an aria-label attribute to the button element that calls
RemoveAttendee(attendee) to provide a descriptive accessible name such as
"Remove attendee" or "Remove [attendee name]" so screen readers can properly
announce the button's purpose to users.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcDateTimePicker.razor.cs`:
- Around line 71-74: The OnFocusOut method closes the picker unconditionally
whenever any internal element loses focus, causing the picker to close when
focus moves between internal controls like the trigger button and time/day
controls. Modify the OnFocusOut method to check whether focus is moving to
another element within the component container before closing the picker. You
should verify if the newly focused element (accessible through FocusEventArgs or
related properties) is still a child of the component, and only set _isOpen to
false when focus actually leaves the entire component bounds.
- Around line 54-57: In the SelectDate method, the new DateTime being
constructed uses date.Kind as the DateTimeKind parameter, which can
inadvertently downgrade a previously set Utc or Local kind to Unspecified. To
preserve the bound DateTimeKind, replace date.Kind with Value.Kind when
constructing the selected DateTime object, ensuring the original kind property
is maintained throughout the selection process.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcDateNavigator.razor`:
- Around line 4-11: The icon-only navigation buttons using
State.NavigatePrevious and State.NavigateNext rely only on the title attribute
for accessibility, which may not be consistently announced by assistive
technologies. Add aria-label attributes to both buttons with descriptive text
matching the button's purpose (e.g., "Previous" and "Next"), and also add
type="button" to both button elements to ensure they are semantically correct
and properly recognized by assistive technologies.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcModeTabs.razor`:
- Around line 8-19: The entire mode tabs container in BitFcModeTabs.razor is
hidden when State.Resources.Count is zero, which traps users in Timeline mode if
resources become empty since they have no UI way to switch back to Event mode.
Remove the outer conditional check on State.Resources.Count so that the mode
tabs remain visible even when resources are empty. Keep the tabs accessible at
all times by rendering the entire div with the foreach loop over _modes and the
State.SetMode(mode) calls regardless of whether State.Resources has items.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcSettings.razor`:
- Around line 12-47: The checkbox inputs in the three dropdown items (for badge
variant toggle, time format toggle, and event layout toggle) and the number
input for day start hour lack proper accessible associations with their labels.
Add unique `id` attributes to each input element (the three checkboxes and the
number input), then update the `label` elements wrapping the checkboxes to
include `for` attributes referencing their corresponding input ids, and for the
number input that doesn't have a wrapping label, add an `aria-labelledby`
attribute that references the id of the span containing DayStartsAtLabel text to
establish the semantic relationship for screen readers.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarColorScheme.cs`:
- Around line 20-28: The constructor in BitFullCalendarColorScheme keeps a live
reference to the caller-provided options list on line 21 by directly assigning
it to the Options property, while the _byId dictionary is built only once during
construction. If the source list is mutated after construction, Options and the
_byId lookup dictionary can become inconsistent. To fix this, create a defensive
copy/snapshot of the options list (using ToList() or similar) before assigning
it to Options, ensuring that both Options and _byId always represent the same
immutable state regardless of external mutations to the original list.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFcAgendaScrollInterop.cs`:
- Line 19: The date.ToString("yyyy-MM-dd") call on the date variable is
culture-sensitive and may produce non-ASCII digits in certain locales, which
will break JavaScript date matching and parsing. Modify the ToString call to
explicitly use CultureInfo.InvariantCulture as the second parameter to ensure
the date is always formatted with ASCII digits in the yyyy-MM-dd format,
regardless of the system locale.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarChangeNotifier.cs`:
- Around line 68-82: The CloneEvent method in BitFullCalendarChangeNotifier
performs a shallow copy of the Data property by directly assigning the reference
instead of creating a deep clone, which violates the deep-snapshot contract.
Modify the Data assignment in the CloneEvent method to deep clone the
source.Data object rather than copying the reference, using an appropriate deep
cloning mechanism such as JSON serialization and deserialization to ensure
mutable objects are not shared between the original and cloned event payloads.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarState.cs`:
- Around line 178-186: The EventsMatch method is using reference equality checks
only, which means when parent components modify existing event instances without
changing the reference, SyncEvents skips the update thinking nothing changed.
Update the EventsMatch method to perform deep content-based comparison of the
event properties instead of just ReferenceEquals checks, so that mutations to
existing items are properly detected. Apply the same fix to the SyncResources
method and its corresponding ResourcesMatch comparison method to ensure
consistent behavior across both event and resource synchronization.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarDayView.razor.cs`:
- Around line 3-90: The BitFcCalendarDayView component displays current events
based on DateTime.Now but never triggers periodic re-renders, causing the
"Happening now" status to become stale during long sessions. Add a timer
mechanism in the component lifecycle (such as in OnInitializedAsync or
OnAfterRenderAsync) that periodically calls StateHasChanged() at regular
intervals (e.g., every minute) to refresh the component and keep the current
event status accurate. Ensure the timer is properly disposed in the component's
cleanup to prevent memory leaks, similar to how the scroll signature is managed
in the existing lifecycle methods.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarTimeline.razor.cs`:
- Around line 13-17: In the timer callback initialization for _timer, both
UpdatePosition() and StateHasChanged() must be wrapped inside a single
InvokeAsync call to ensure they execute on the Blazor renderer thread. Currently
UpdatePosition() is called directly on the timer thread before being marshaled,
which can cause race conditions since _positionPx is modified. Move the
UpdatePosition() call inside the InvokeAsync delegate so that both methods
execute atomically on the renderer thread, preventing state modification outside
the renderer's synchronization context.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarWeekView.razor`:
- Around line 43-47: The week time slot divs with class bit-bfc-hour-row are
currently only clickable, which excludes keyboard users. Add a keyboard event
handler by binding `@onkeydown` to a new method (OnHourSlotKeyDownAsync) that
accepts KeyboardEventArgs along with the day and hour parameters, and invoke the
existing OnHourClickAsync method when the Enter or Space keys are pressed. Apply
this keyboard handler binding to both affected div elements in the week view
grid (the ones currently showing `@onclick`="() => OnHourClickAsync(d, hour)").
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcEventBlock.razor`:
- Around line 28-35: The BitFcEventBlock event container div is not
keyboard-accessible, preventing keyboard users from activating event details.
Add the tabindex="0" attribute to the div element to make it focusable, then add
the `@onkeydown`="OnKeyDown" event binding to the same div. Implement the
OnKeyDown method that takes KeyboardEventArgs as a parameter and calls the
existing OnClick method when the user presses Enter or Space keys.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcMiniCalendar.razor`:
- Around line 15-21: The icon-only buttons for PrevMonth and NextMonth
navigation lack accessible labels for screen readers, making them inaccessible.
Add aria-label attributes to both buttons with descriptive text like "Previous
month" and "Next month" respectively. Additionally, the day selection elements
(the clickable span elements referenced at lines 43-45) need keyboard support -
convert them to button elements or add keyboard event handlers (onkeydown) to
make them accessible via keyboard navigation, ensuring they can be focused and
activated with Enter/Space keys like standard controls.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcMiniCalendar.razor.cs`:
- Around line 9-15: The `_displayMonth` field is initialized only once in
`OnInitialized()` and can become out of sync with `State.SelectedDate` when the
selected date changes externally. Override the `OnParametersSet()` lifecycle
method in the BitFcMiniCalendar component to synchronize `_displayMonth`
whenever parameters change, using the same logic that calculates `_displayMonth`
from `State.SelectedDate` as in `OnInitialized()`. This ensures `_displayMonth`
stays synchronized with external changes to the selected date.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcDayCell.razor`:
- Around line 8-12: The clickable div elements in the BitFcDayCell component
(around line 8 with the OnCellClick handler and the additional clickable element
around line 44) do not support keyboard navigation and activation, making them
inaccessible to keyboard users. Convert these interactive divs to proper
keyboard-accessible controls by adding a tabindex attribute to make them
focusable, an ARIA role to identify them as interactive elements, and keyboard
event handlers (`@onkeydown` or `@onkeyup`) that trigger the same OnCellClick action
when Enter or Space keys are pressed, matching the click behavior.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcMonthEventBadge.razor`:
- Around line 19-25: The BitFcMonthEventBadge component's event badge div is not
keyboard-accessible. Add tabindex="0" to the div element to make it focusable,
and add an `@onkeydown` event handler that detects Enter or Space key presses and
invokes OnSelected.InvokeAsync(Event) when those keys are pressed, matching the
behavior of the existing click handler for keyboard users.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineDayView.razor.cs`:
- Line 18: The constant _scrollContainerId in the BitFcTimelineDayView component
uses a fixed DOM id that can collide when multiple calendar instances render on
the same page. Change _scrollContainerId from a constant to an instance field
that generates a unique identifier per component instance (such as using a GUID
or unique suffix), ensuring each calendar instance has its own unique scroll
container id to prevent the scrollTimelineToTarget method from operating on the
wrong element.
- Around line 72-85: The OnSlotClickAsync method accepts a minute parameter but
discards it in the internal add flow when OnAddClick is not delegated. Store the
minute value by adding a field similar to _addStartHour (for example,
_addStartMinute), then assign the minute parameter to this field when setting
_showAddDialog to true. Next, pass this stored minute value to the
BitFcAddEditEventDialog component by updating its parameters, and ensure the
dialog component supports and respects the minute parameter when initializing
the time selection to preserve half-hour slot precision.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineEventBlock.razor.cs`:
- Around line 71-74: The catch block in BitFcTimelineEventBlock.razor.cs
currently only catches JSException, but common Blazor lifecycle exceptions like
JSDisconnectedException, InvalidOperationException, and
OperationCanceledException can also be thrown during interop calls and will
crash the render if not handled. Expand the catch clause to handle all these
exception types alongside JSException so they are properly swallowed and the
render retry logic is executed for all expected failure scenarios.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineLayout.razor`:
- Around line 23-27: The `!hasResources` conditional in
BitFcTimelineLayout.razor prevents the rendering of unassigned event rows
because the else block never executes when hasResources is false. Restructure
the logic to ensure that the unassigned-row rendering (referenced at lines
58-70) is not blocked by the !hasResources check. Either move the unassigned row
rendering outside of or before the hasResources conditional, or add a separate
condition that allows unassigned events to display independently of whether
resources exist.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineMonthView.razor.cs`:
- Line 18: The _scrollContainerId constant in BitFcTimelineMonthView is
hardcoded, causing ID collisions when multiple calendar instances are rendered.
Replace the const declaration with a property or field that generates a unique
identifier per component instance (such as using a Guid or unique prefix based
on component state), ensuring each rendered timeline month view has a distinct
scroll container id that won't conflict with other instances on the same page.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineWeekView.razor`:
- Around line 21-26: The code filters out null/empty resource IDs on line 21,
but then uses unguarded dictionary lookups with resource.Id and rowKey as keys
on lines 60, 91, and 126 without verifying they are not null or empty. Add guard
conditions before each dictionary lookup at these locations to check if the
resource ID or rowKey is null or empty, and either skip the operation or provide
a safe fallback to prevent runtime exceptions when rendering rows with invalid
resource identifiers.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineWeekView.razor.cs`:
- Line 18: The constant _scrollContainerId with a hardcoded value
"bit-bfc-tl-week-scroll" will cause id collisions when multiple calendar
instances are rendered on the same page. Replace this constant with a
per-instance field that generates a unique scroll container id for each
BitFcTimelineWeekView component instance, such as using a generated guid or
unique identifier that is assigned when the component initializes.
- Around line 74-87: The OnSlotClickAsync method receives a minute parameter but
only uses it when OnAddClick has a delegate. In the fallback branch where the
built-in add dialog is shown (lines 84-86), the minute value is ignored. Store
the minute parameter in a new field (similar to how _addStartHour stores the
hour) such as _addStartMinute, and ensure this field is set when _showAddDialog
is set to true. Additionally, verify that the BitFcAddEditEventDialog component
uses this _addStartMinute field when initializing the event time, adding support
for StartMinute if it does not currently exist.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/YearView/BitFcCalendarYearView.razor`:
- Around line 21-22: The div elements with click handlers at lines 21-22 and
40-41 in BitFcCalendarYearView.razor lack keyboard accessibility, preventing
keyboard-only users from navigating. Add keyboard event support to both div
elements by implementing an onkeydown event handler that triggers the same
action (GoToMonth call at line 21 and the day/event navigation at line 40) when
Enter or Space keys are pressed. Additionally, add appropriate ARIA attributes
(role and tabindex) to make these divs focusable and semantically indicate they
are interactive elements to assistive technologies.
- Around line 37-38: The dayEvents filter in BitFcCalendarYearView.razor uses
only StartDate.Date comparison, which excludes multi-day events from
intermediate and end dates. Modify the Where clause to check if cell.Date falls
within a date-range overlap by comparing against both the event's StartDate and
EndDate properties, ensuring a multi-day event is included on every day it
spans, not just its start date.
In `@src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss`:
- Line 7: The stylelint rule scss/load-partial-extension is being violated in
the extra-components.scss file. Remove the explicit .scss file extension from
the `@import` statement for BitFullCalendar - the stylelint rule requires that
SCSS partials be imported without their file extension, so the import should
reference only the directory path and filename without the .scss suffix.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/FullCalendar/BitFullCalendarDemo.razor`:
- Around line 18-23: The BitLink component in BitFullCalendarDemo.razor has
Target="_blank" set to open an external link in a new tab, but it is missing the
rel attribute required for security. Add rel="noopener noreferrer" to the
BitLink component that points to the NuGet package URL to prevent
reverse-tabnabbing vulnerabilities and properly isolate the opener context when
opening external links in a new tab.
---
Minor comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventDetailsDialog.razor.cs`:
- Around line 19-23: The OnEditClose() method invokes the OnClose callback
asynchronously but does not await the result, which can cause the callback to
not complete properly. Change the OnEditClose() method signature from void to
async Task, and add the await keyword before the OnClose.InvokeAsync() call to
ensure the asynchronous operation completes before the method returns.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventListDialog.razor`:
- Line 6: In the BitFcEventListDialog.razor component's h3 element, the date
formatting using Date.ToString("dddd, MMM d, yyyy") does not respect the
calendar's culture setting. Modify the Date.ToString call to include
State.Culture as a culture parameter so that the date is formatted according to
the calendar's culture instead of the default culture. This will ensure the date
header displays in the correct localized format.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcCalendarHeader.razor`:
- Around line 20-23: The button element with the OnAddEventClick handler in
BitFcCalendarHeader.razor lacks an explicit type attribute, causing it to
default to type="submit" per HTML specification. This can trigger unintended
form submission if the FullCalendar component is placed within a form context.
Add type="button" explicitly to the button element that contains the
OnAddEventClick event handler to ensure it only calls the click handler and does
not submit any parent form.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarChangeEventArgs.cs`:
- Line 49: Add the `required` keyword to the Source property in the
BitFullCalendarChangeEventArgs class to force callers to explicitly assign a
BitFullCalendarChangeSource value, preventing silent defaults to Dialog (the
zero enum value) which can cause incorrect origin metadata in change payloads.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarEvent.cs`:
- Around line 25-27: The IsSingleDay property at line 25 of BitFullCalendarEvent
class does not treat midnight EndDate as exclusive, causing events ending at
00:00 of the next day to be incorrectly classified as multi-day. Fix this by
adjusting the Date comparison in the IsSingleDay property to subtract one tick
or unit of time from EndDate before comparing dates, ensuring that an EndDate at
midnight is treated as the end of the previous day rather than the start of the
next day.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarHelpers.cs`:
- Around line 213-218: The FormatTime method is using
CultureInfo.InvariantCulture for both the 24-hour and 12-hour time format calls,
which prevents localization for non-English cultures. Replace
CultureInfo.InvariantCulture with CultureInfo.CurrentCulture in both
date.ToString() calls within the FormatTime method to respect the user's active
culture settings. Apply the same fix to the similar time formatting code
mentioned in the "Also applies to" section around lines 248-254.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarWeekView.razor.cs`:
- Around line 36-49: The OnHourClickAsync method only calls
State.SetSelectedDate(day) when OnAddClick has a delegate, but not when using
the built-in add dialog (when _addDate, _addHour, and _showAddDialog are set).
Move the State.SetSelectedDate(day) call outside of the if block so it executes
regardless of which path is taken, ensuring the SelectedDate remains consistent
across both the delegated callback path and the built-in dialog path.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineDayView.razor`:
- Around line 60-61: The preview styling for drag operations in
BitFcTimelineDayView.razor is computed without verifying that an active drag is
in progress, allowing stale preview highlighting to persist after a drag is
canceled. Add a check to verify that a drag operation is currently active before
setting isPreviewHour and isPreviewHalf variables around lines 60-61.
Additionally, apply the same fix to the similar preview checks mentioned at
lines 95-96. The condition should verify that _dragResourceId is not null or
empty and that an active drag state exists before allowing the preview styles to
be applied.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineMonthView.razor`:
- Around line 79-80: The isPreview condition on lines 79-80 (and also on lines
106-107) only checks if the current cell matches the drag source cell, but does
not verify if an active drag operation is in progress. When a drag is cancelled,
these variables retain their values, causing stale preview highlights to remain.
Add a check for an active drag state (such as a boolean flag tracking whether a
drag is currently in progress) to the isPreview condition so that the preview
class is only applied when there is both an active drag operation and the cell
matches the drag source.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/YearView/BitFcCalendarYearView.razor`:
- Around line 8-10: Replace the hardcoded value 12 in the Enumerable.Range call
within the months variable assignment with a dynamic call to the calendar's
month count method. Instead of Range(1, 12), use Range(1,
cal.GetMonthsInYear(culturalYear)) or the appropriate equivalent method on the
cal object to retrieve the actual number of months in the given year, ensuring
the code correctly handles calendars with varying month counts.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/FullCalendar/BitFullCalendarDemo.razor`:
- Line 5: The Description attribute text in the BitFullCalendarDemo.razor
component has awkward wording and inconsistent casing. Update the Description
property to use proper casing for "FullCalendar" (currently lowercase) and
simplify the text to remove redundancy. Instead of "fullcalendar component of
the bit BlazorUI components", use clearer phrasing such as "FullCalendar
component for Bit BlazorUI" to improve readability and consistency for
user-facing documentation.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarColorScheme.cs`:
- Around line 90-101: The GetSortOrder method in BitFullCalendarColorScheme
currently uses StringComparer.OrdinalIgnoreCase.GetHashCode() for unknown color
IDs, which is non-deterministic across process runs and doesn't implement
text-based sorting as documented. Replace the hash-based fallback on line 101
with a deterministic approach that sorts unknown IDs lexically by their string
value instead of relying on hash codes, such as computing a value based on the
ordinal character values of the trimmed string or implementing a stable
text-based comparison alternative.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor`:
- Around line 11-16: The FirstOrDefault call over _weekEvents is executed inside
nested loops for each day and row, causing quadratic time complexity during
rerenders. Create a precomputed lookup dictionary in the OnParametersSet
lifecycle method that maps (date, row) tuples to events based on the same
filtering criteria (checking _eventRows values and date ranges with
StartDate.Date and EndDate.Date comparisons). Then replace the FirstOrDefault
logic in the render loop with a simple O(1) dictionary lookup using the current
day and row as the key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e0daf90b-d30d-468a-b05d-3f9abc65f4a8
📒 Files selected for processing (100)
src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarBody.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarBody.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarToast.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarToast.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFullCalendar.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcAddEditEventDialog.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcAddEditEventDialog.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcDateTimePicker.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcDateTimePicker.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventDetailsDialog.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventDetailsDialog.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventListDialog.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Dialogs/BitFcEventListDialog.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/DragDrop/BitFcDraggableEvent.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/DragDrop/BitFcDraggableEvent.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/DragDrop/BitFcDroppableArea.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/DragDrop/BitFcDroppableArea.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Enums/BitFullCalendarAgendaGroupBy.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Enums/BitFullCalendarBadgeVariant.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Enums/BitFullCalendarEventLayout.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Enums/BitFullCalendarMode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Enums/BitFullCalendarView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcCalendarHeader.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcCalendarHeader.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcDateNavigator.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcDateNavigator.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcFilterEvents.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcFilterEvents.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcModeTabs.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcModeTabs.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcSettings.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcSettings.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcTodayButton.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcTodayButton.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcViewTabs.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Header/BitFcViewTabs.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarAttendee.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarCell.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarChangeEventArgs.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarColorOption.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarColorScheme.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarDateChangeEventArgs.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarEvent.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarOptions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarResource.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Models/BitFullCalendarTexts.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFcAgendaScrollInterop.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFcTimeGridScrollInterop.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFcTimelineScrollInterop.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarChangeNotifier.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Services/BitFullCalendarState.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/AgendaView/BitFcAgendaEvents.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/AgendaView/BitFcAgendaEvents.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarDayView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarDayView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarTimeline.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarTimeline.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarWeekView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcCalendarWeekView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcDayViewMultiDayEventsRow.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcDayViewMultiDayEventsRow.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcEventBlock.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcEventBlock.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcMiniCalendar.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcMiniCalendar.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcRenderGroupedEvents.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcRenderGroupedEvents.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcTimeColumn.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcTimeColumn.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/DayWeekView/BitFcWeekViewMultiDayEventsRow.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcCalendarMonthView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcCalendarMonthView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcDayCell.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcDayCell.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcEventBullet.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcEventBullet.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcMonthEventBadge.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/MonthView/BitFcMonthEventBadge.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineDayView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineDayView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineEventBlock.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineEventBlock.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineLayout.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineLayout.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineMonthView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineMonthView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineWeekView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/TimelineMode/BitFcTimelineWeekView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/YearView/BitFcCalendarYearView.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/Views/YearView/BitFcCalendarYearView.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scsssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/FullCalendar/BitFullCalendarDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/FullCalendar/BitFullCalendarDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
| public void Show(string message, bool isError = false) | ||
| { | ||
| var item = new ToastItem { Id = _nextId++, Message = message, IsError = isError }; | ||
| _toasts.Add(item); | ||
| StateHasChanged(); | ||
| _ = RemoveAfterDelay(item.Id); | ||
| } | ||
|
|
||
| private async Task RemoveAfterDelay(int id) | ||
| { | ||
| await Task.Delay(3000); | ||
| _toasts.RemoveAll(t => t.Id == id); | ||
| await InvokeAsync(StateHasChanged); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "BitFcCalendarToast.razor.cs" | head -20Repository: bitfoundation/bitplatform
Length of output: 156
🏁 Script executed:
cat -n ./src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarToast.razor.csRepository: bitfoundation/bitplatform
Length of output: 1023
🏁 Script executed:
ls -la ./src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/ | grep -i toastRepository: bitfoundation/bitplatform
Length of output: 224
🏁 Script executed:
cat -n ./src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarToast.razorRepository: bitfoundation/bitplatform
Length of output: 374
Move toast list mutations onto the renderer dispatcher and cancel delayed removals on dispose.
RemoveAfterDelay mutates _toasts without dispatcher protection (line 19), creating a race condition with the Razor template's foreach enumeration that can cause runtime failures. Additionally, delayed removal tasks are not canceled when the component is disposed, causing them to execute after the component is torn down.
🛠️ Suggested fix
-public partial class BitFcCalendarToast
+public partial class BitFcCalendarToast : IDisposable
{
private readonly List<ToastItem> _toasts = [];
+ private readonly CancellationTokenSource _disposeCts = new();
private int _nextId;
public void Show(string message, bool isError = false)
{
var item = new ToastItem { Id = _nextId++, Message = message, IsError = isError };
_toasts.Add(item);
StateHasChanged();
- _ = RemoveAfterDelay(item.Id);
+ _ = RemoveAfterDelay(item.Id, _disposeCts.Token);
}
- private async Task RemoveAfterDelay(int id)
+ private async Task RemoveAfterDelay(int id, CancellationToken cancellationToken)
{
- await Task.Delay(3000);
- _toasts.RemoveAll(t => t.Id == id);
- await InvokeAsync(StateHasChanged);
+ try
+ {
+ await Task.Delay(3000, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ return;
+ }
+
+ await InvokeAsync(() =>
+ {
+ _toasts.RemoveAll(t => t.Id == id);
+ StateHasChanged();
+ });
}
+
+ public void Dispose() => _disposeCts.Cancel();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/FullCalendar/BitFcCalendarToast.razor.cs`
around lines 8 - 21, The RemoveAfterDelay method mutates _toasts without
dispatcher protection on line 19, creating a race condition with the Razor
template's foreach loop. Additionally, delayed removal tasks are never canceled
when the component is disposed. To fix this, wrap the _toasts.RemoveAll() call
inside RemoveAfterDelay with InvokeAsync() to ensure it runs on the renderer
dispatcher. Next, create a collection to track CancellationTokenSource objects
for each delayed removal task, modify RemoveAfterDelay to accept a
CancellationToken and check it after the Task.Delay, and implement
IAsyncDisposable on the component class to cancel all pending removal tasks when
the component is disposed.
closes #12449
Summary by CodeRabbit