Skip to content

Fix positioning issues of callouts (#11183)#12302

Open
msynk wants to merge 5 commits intobitfoundation:developfrom
msynk:11183-blazorui-callout-positioning-issues
Open

Fix positioning issues of callouts (#11183)#12302
msynk wants to merge 5 commits intobitfoundation:developfrom
msynk:11183-blazorui-callout-positioning-issues

Conversation

@msynk
Copy link
Copy Markdown
Member

@msynk msynk commented May 4, 2026

closes #11183

Summary by CodeRabbit

  • Refactor
    • Overlay and callout elements were restructured to render as siblings of their trigger containers, enabling popovers to be moved into the document body and restored while preserving behavior and interactions.
  • Style
    • Calendar-related styles were scoped so stationary/standalone layout rules apply only when the component explicitly uses the standalone state.
  • Chores / Tests
    • Tests updated to match revised overlay/callout markup and IDs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Walkthrough

Multiple Blazor callout-based components now render their overlay and callout elements as DOM siblings outside their root wrappers instead of nesting them inside. Each component tracks a dedicated _overlayId and passes it to JavaScript interop, enabling the JS layer to move callouts and overlays to document.body when opened and restore them when closed, addressing styling issues in nested callout scenarios.

Changes

Callout Repositioning & Overlay Tracking Refactor

Layer / File(s) Summary
Field Addition
src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor.cs, src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor.cs
New private _overlayId field added to each component to track a dedicated overlay element identifier.
Lifecycle Initialization
Same 10 .razor.cs files
During OnInitialized() or OnInitializedAsync(), each component now initializes _overlayId (e.g., BitMenuButton-{UniqueId}-overlay, {_datePickerId}-overlay).
Razor Markup Restructure
src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor, src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor, src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor, src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor, src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor, src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor, src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor, src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor
Overlay and callout <div> elements moved from being nested inside root containers to rendering as DOM siblings after the root container closes. Each overlay now includes id="@_overlayId".
DatePicker Formatting
src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
Whitespace and formatting reformatting; no functional changes to conditional rendering or UI logic.
JS Interop Contract
src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/CalloutsJsRuntimeExtensions.cs
BitCalloutToggleCallout<T> method signature extended with new string overlayId parameter, passed to JavaScript toggle call.
Core Toggle Implementation
src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts
Callouts.toggle now moves callout and overlay to document.body on open (via moveCalloutToBody) and restores them to original parent on close (via restoreCalloutToOriginalParent). New _calloutOriginalParents map tracks original DOM positions and next sibling references. Callouts.replaceCurrent also restores previous callout.
Style Scoping
src/BlazorUI/Bit.BlazorUI/Components/Inputs/*/*.scss
Calendar/container display/position rules narrowed to combined selectors (e.g., .bit-dtp-cal.bit-dtp-sta) so styling applies only when .bit-*-sta is present.
Test Updates
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Navs/DropMenu/BitDropMenuTests.cs
Expected markup in BitDropMenuShouldRespectHtmlAttributes test updated to reflect new overlay id attribute and adjusted class whitespace formatting.

Sequence Diagram

sequenceDiagram
    participant User as User/UI
    participant Component as Blazor Component
    participant DOM as Original DOM
    participant JS as JavaScript Layer
    participant Body as document.body

    Note over Component: Open Callout
    User->>Component: Toggle Callout
    Component->>Component: Initialize _overlayId (if needed)
    Component->>JS: BitCalloutToggleCallout(calloutId, overlayId, isOpen=true)
    
    rect rgba(100, 200, 100, 0.5)
    Note over JS: Move to body on open
    JS->>JS: moveCalloutToBody(calloutId, overlayId)
    JS->>DOM: Store callout & overlay original parent/nextSibling
    JS->>Body: appendChild(callout)
    JS->>Body: appendChild(overlay)
    end
    
    JS->>JS: Position & size callout
    JS->>Component: Callout positioned at body level
    Component->>User: Overlay + Callout visible

    Note over Component: Close Callout
    User->>Component: Close Callout
    Component->>JS: BitCalloutToggleCallout(calloutId, overlayId, isOpen=false)
    
    rect rgba(200, 100, 100, 0.5)
    Note over JS: Restore from body on close
    JS->>JS: restoreCalloutToOriginalParent(calloutId)
    JS->>DOM: Reinsert callout & overlay at original positions
    JS->>JS: reset() - hide callout & overlay
    end
    
    JS->>Component: Restored & hidden
    Component->>User: Callout back in original DOM location
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hopping through the DOM so free,
Callouts dance from branch to tree,
To document.body they leap and bound,
Restoring peace where nesting's found!
Siblings shining, tidy and sound.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Fix positioning issues of callouts' directly summarizes the main change—addressing callout positioning problems when Bit components with callouts are nested.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #11183 by restructuring overlays/callouts as siblings, tracking separate overlay IDs, and updating JS interop and SCSS rules to handle nested callout positioning correctly.
Out of Scope Changes check ✅ Passed All changes directly support fixing nested callout positioning: DOM restructuring, overlay ID tracking, JS interop updates, and CSS adjustments are all necessary for the stated objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@msynk msynk marked this pull request as ready for review May 5, 2026 20:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses nested Bit component callout styling/positioning issues (e.g., BitSearchBox inside BitPanel) by ensuring callout elements (and their overlays) can be moved out of potentially clipping/positioned parent DOM contexts and positioned consistently.

Changes:

  • Update the shared callout JS logic to temporarily move callout (and overlay) elements to document.body, then restore them to their original DOM positions when closed.
  • Extend the callout JS interop contract to pass an overlayId, and update multiple components to generate/render unique overlay element IDs.
  • Adjust a bUnit markup test to reflect the updated DOM structure for BitDropMenu.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Navs/DropMenu/BitDropMenuTests.cs Updates expected rendered markup to match new overlay/callout DOM placement.
src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts Moves callout/overlay nodes to body on open and restores them on close; updates current-callout replacement behavior.
src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/CalloutsJsRuntimeExtensions.cs Adds overlayId to the JS interop method signature and invocation.
src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor.cs Generates a stable overlay element ID and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor Renders overlay with an id so JS can move/restore it alongside the callout.
src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor Renders overlay with an id and places overlay/callout outside the root wrapper.
src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor Renders overlay with an id and places overlay/callout outside the root wrapper.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor Renders overlay with an id so JS can move/restore it.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor Renders overlay with an id and places overlay/callout outside the root wrapper.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor Renders overlay with an id and places overlay/callout outside the root wrapper.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor Renders overlay with an id so JS can move/restore it.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor Renders overlay with an id so JS can move/restore it.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor Renders overlay with an id so JS can move/restore it.
src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor.cs Adds overlay ID generation and passes it to JS toggle.
src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor Renders overlay with an id and places overlay/callout outside the root wrapper.

Comment thread src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/Scripts/Callouts.ts`:
- Around line 196-207: The current insertBefore calls can throw NotFoundError if
recorded siblings were removed; update the insertion logic around
callout/original.overlay so that before calling insertBefore you verify the
sibling is still a child of the expected parent (e.g., check
original.nextSibling && original.nextSibling.parentNode === original.parent) and
only then call original.parent.insertBefore(callout, original.nextSibling),
otherwise fall back to original.parent.appendChild(callout); apply the same
guard for original.overlayNextSibling/original.overlayParent when inserting
original.overlay to avoid using a stale reference.
🪄 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: 9ff7efca-49cf-4c04-91f1-45d2174ef4d8

📥 Commits

Reviewing files that changed from the base of the PR and between beefafa and 1f2a760.

📒 Files selected for processing (23)
  • src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/CalloutsJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Navs/DropMenu/BitDropMenuTests.cs

Comment thread src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts Outdated
@msynk msynk requested a review from Copilot May 9, 2026 16:39
@msynk
Copy link
Copy Markdown
Member Author

msynk commented May 9, 2026

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

@msynk: Sure, I'll re-review the changes in this PR now!

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

@msynk: Sure, I'll re-review the changes in this PR now!

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.

Comment thread src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor Outdated
Comment thread src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/Components/Inputs/SearchBox/BitSearchBox.razor`:
- Around line 113-116: Remove the redundant empty conditional block that checks
"(SuggestItems?.Any() ?? false) || SuggestItemsProvider is not null" in
BitSearchBox.razor; specifically delete the empty if-statement so only the
remaining conditional (the one at the later occurrence) remains, leaving the
component logic unaffected and avoiding duplicated checks for SuggestItems and
SuggestItemsProvider.

In `@src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor`:
- Around line 68-73: The breadcrumb regular item is being rendered with overflow
styling because the call uses GetClasses(item, true); change that to use the
non-overflow variant by calling GetClasses(item, false) for normal clickable
items; update the button's class attribute in BitBreadcrumb.razor (where
GetIsSelected, GetIsEnabled, GetStyles and HandleOnItemClick are used) so
non-overflow items receive GetClasses(item, false) instead of GetClasses(item,
true).
🪄 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: 71cadab6-8a33-45e8-b2b8-46a687c108ed

📥 Commits

Reviewing files that changed from the base of the PR and between beefafa and d645079.

📒 Files selected for processing (23)
  • src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/CalloutsJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Navs/DropMenu/BitDropMenuTests.cs

Comment thread src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor Outdated
Comment thread src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor Outdated
@msynk msynk requested a review from Copilot May 9, 2026 18:24
@msynk
Copy link
Copy Markdown
Member Author

msynk commented May 9, 2026

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

@msynk: Sure, I'll re-review the changes in this PR now!

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts (1)

237-241: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Potential memory leak: clean up Map entries on callout disposal.

The _calloutOriginalParents Map is not cleaned up when a callout is disposed unless it is the currently open callout. If a component with a callout is destroyed while another callout is open, or if the callout element is removed from the DOM before restoreCalloutToOriginalParent is called, the Map will retain references to DOM nodes indefinitely, causing a memory leak.

🧹 Suggested cleanup

Update the clear method to always remove the entry from the Map:

 public static clear(calloutId: string) {
+    Callouts._calloutOriginalParents.delete(calloutId);
+
     if (Callouts.current.calloutId !== calloutId) return;
 
     Callouts.replaceCurrent();
 }
🤖 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/Scripts/Callouts.ts` around lines 237 - 241, The
clear method currently only calls Callouts.replaceCurrent when the given
calloutId matches the open one, leaving entries in the _calloutOriginalParents
Map and causing a leak; update Callouts.clear to always remove the Map entry for
the provided calloutId (e.g., _calloutOriginalParents.delete(calloutId))
regardless of whether Callouts.current.calloutId matches, and then proceed with
existing logic (calling Callouts.replaceCurrent or
restoreCalloutToOriginalParent as appropriate) so stored DOM references are
cleaned up when a callout is disposed.
🤖 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/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor`:
- Around line 79-82: The overlay div that uses `@onclick`="CloseCallout" (id
`@_overlayId`, class Classes?.Overlay, style Styles?.Overlay, shown when IsOpen)
is missing ARIA attributes; update the element to include an appropriate role
(e.g., role="button" if it's interactive or role="presentation" if it should be
ignored) and an aria-label (e.g., aria-label="Close overlay") and ensure it is
keyboard accessible (e.g., add tabindex="0" and wire key handlers to invoke
CloseCallout) so screen readers and keyboard users can discover and activate the
overlay.

In `@src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor`:
- Around line 199-205: The responsive close button in BitDropdown.razor (the
<button `@onclick`="CloseCallout" ... /> element) has invalid ARIA attributes
aria-hidden="Close" and aria-describedby="Close"; remove both attributes (they
are unnecessary since aria-label="Close" is present) so the button only keeps
aria-label="Close" (and other valid attributes like type, title, style),
ensuring valid accessibility markup.

In `@src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor`:
- Around line 144-148: Remove the incorrect ARIA role by deleting role="listbox"
from the callout div in BitBreadcrumb.razor (the element using `@_calloutId`,
Styles?.Callout and Classes?.Callout); keep the rest of the markup for overflow
items intact so links/buttons/spans retain their aria-current attributes. Also
verify the breadcrumb component is rendered inside a <nav> landmark (or wrap the
top-level breadcrumb output in a <nav> if it isn't) to provide proper navigation
semantics.

In `@src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts`:
- Around line 171-188: The code uses a non-null assertion on
callout.parentElement in moveCalloutToBody which will throw if the element is
detached; update moveCalloutToBody to guard for a null parentElement by checking
callout.parentElement before using it (or set parent and nextSibling to null
when detached) and then populate Callouts._calloutOriginalParents with those
safe values (use calloutId, callout, overlay and
overlayParent/overlayNextSibling as before) so no non-null assertion is used and
no runtime error occurs when callout is detached.

---

Outside diff comments:
In `@src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts`:
- Around line 237-241: The clear method currently only calls
Callouts.replaceCurrent when the given calloutId matches the open one, leaving
entries in the _calloutOriginalParents Map and causing a leak; update
Callouts.clear to always remove the Map entry for the provided calloutId (e.g.,
_calloutOriginalParents.delete(calloutId)) regardless of whether
Callouts.current.calloutId matches, and then proceed with existing logic
(calling Callouts.replaceCurrent or restoreCalloutToOriginalParent as
appropriate) so stored DOM references are cleaned up when a callout is disposed.
🪄 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: 14f0baa6-cfc8-4a54-813e-31b42e8843a8

📥 Commits

Reviewing files that changed from the base of the PR and between beefafa and af8d2a7.

📒 Files selected for processing (26)
  • src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Buttons/MenuButton/BitMenuButton.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.scss
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.scss
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/DateRangePicker/BitDateRangePicker.scss
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/SearchBox/BitSearchBox.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Inputs/TimePicker/BitTimePicker.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Navs/DropMenu/BitDropMenu.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Callout/BitCallout.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Extensions/JsInterop/CalloutsJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Navs/DropMenu/BitDropMenuTests.cs

Comment on lines +79 to +82
<div @onclick="CloseCallout"
style="display:@(IsOpen ? "block" : "none"); @Styles?.Overlay"
id="@_overlayId"
class="bit-ctp-ovl @Classes?.Overlay"></div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider adding accessibility attributes to the overlay.

The overlay div is interactive (@onclick="CloseCallout") but lacks ARIA attributes to communicate its purpose to assistive technologies. Consider adding role="presentation" or role="button" and an aria-label to improve screen reader experience.

♿ Proposed accessibility enhancement
 <div `@onclick`="CloseCallout"
      style="display:@(IsOpen ? "block" : "none"); `@Styles`?.Overlay"
      id="@_overlayId"
+     role="presentation"
+     aria-label="Close time picker"
      class="bit-ctp-ovl `@Classes`?.Overlay"></div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div @onclick="CloseCallout"
style="display:@(IsOpen ? "block" : "none"); @Styles?.Overlay"
id="@_overlayId"
class="bit-ctp-ovl @Classes?.Overlay"></div>
<div `@onclick`="CloseCallout"
style="display:@(IsOpen ? "block" : "none"); `@Styles`?.Overlay"
id="@_overlayId"
role="presentation"
aria-label="Close time picker"
class="bit-ctp-ovl `@Classes`?.Overlay"></div>
🤖 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/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor`
around lines 79 - 82, The overlay div that uses `@onclick`="CloseCallout" (id
`@_overlayId`, class Classes?.Overlay, style Styles?.Overlay, shown when IsOpen)
is missing ARIA attributes; update the element to include an appropriate role
(e.g., role="button" if it's interactive or role="presentation" if it should be
ignored) and an aria-label (e.g., aria-label="Close overlay") and ensure it is
keyboard accessible (e.g., add tabindex="0" and wire key handlers to invoke
CloseCallout) so screen readers and keyboard users can discover and activate the
overlay.

Comment on lines +199 to +205
<button @onclick="CloseCallout"
type="button"
title="Close"
aria-label="Close"
aria-hidden="Close"
aria-describedby="Close"
style="@Styles?.ResponsiveCloseButton"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n src/BlazorUI/Bit.BlazorUI/Components/Inputs/Dropdown/BitDropdown.razor | sed -n '195,210p'

Repository: bitfoundation/bitplatform

Length of output: 827


Fix invalid ARIA attributes on the responsive close button.

Lines 203–204 contain invalid ARIA attribute values. aria-hidden="Close" must be a boolean (true or false), and aria-describedby="Close" must reference actual element IDs rather than plain text. Since the button already has aria-label="Close", both attributes can be safely removed.

♿ Suggested fix
             <button `@onclick`="CloseCallout"
                     type="button"
                     title="Close"
                     aria-label="Close"
-                    aria-hidden="Close"
-                    aria-describedby="Close"
                     style="@Styles?.ResponsiveCloseButton"
                     class="bit-drp-cls `@Classes`?.ResponsiveCloseButton">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button @onclick="CloseCallout"
type="button"
title="Close"
aria-label="Close"
aria-hidden="Close"
aria-describedby="Close"
style="@Styles?.ResponsiveCloseButton"
<button `@onclick`="CloseCallout"
type="button"
title="Close"
aria-label="Close"
style="@Styles?.ResponsiveCloseButton"
🤖 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/Components/Inputs/Dropdown/BitDropdown.razor`
around lines 199 - 205, The responsive close button in BitDropdown.razor (the
<button `@onclick`="CloseCallout" ... /> element) has invalid ARIA attributes
aria-hidden="Close" and aria-describedby="Close"; remove both attributes (they
are unnecessary since aria-label="Close" is present) so the button only keeps
aria-label="Close" (and other valid attributes like type, title, style),
ensuring valid accessibility markup.

Comment on lines +144 to +148
<div id="@_calloutId"
tabindex="0"
role="listbox"
style="@Styles?.Callout"
class="bit-brc-cal @Classes?.Callout">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

What is the correct ARIA role for a breadcrumb overflow menu that contains navigation links and clickable items?

💡 Result:

For a breadcrumb overflow menu that contains navigation links and clickable items, the correct ARIA role is not "menu" or "menuitem" for the breadcrumb items themselves. Breadcrumbs are navigation, not menus. The WAI-ARIA Breadcrumb Pattern uses a labelled nav element containing links, with aria-current="page" on the last link [1]. No menu roles are used; separators are CSS-only [1]. ARIA explicitly advises against using role="menu" for site navigation; reserve it for composite widgets with roving focus [2]. role="menuitem" requires a menu parent [3]. For overflow: - Use a button trigger (e.g., "More" with aria-expanded) to show additional breadcrumb links. - Keep overflow items as standard links (no menu/menuitem roles) [1][4][5]. - If implementing full menu keyboard behavior, use role="menu" on the popup and "menuitem" on items, but this is uncommon for breadcrumbs [2][3]. Best practice: plain links in the overflow, within the nav landmark [1].

Citations:


🏁 Script executed:

cat -n src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor | sed -n '140,160p'

Repository: bitfoundation/bitplatform

Length of output: 1285


🏁 Script executed:

# Also check what elements are in the overflow region around lines 156-228
cat -n src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor | sed -n '144,165p'

Repository: bitfoundation/bitplatform

Length of output: 1337


🏁 Script executed:

# Check if the callout is within a nav element and look for trigger button
cat -n src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor | sed -n '1,145p' | tail -50

Repository: bitfoundation/bitplatform

Length of output: 2368


🏁 Script executed:

# Also check if there's visibility control via CSS or JS
cat -n src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor | sed -n '100,149p'

Repository: bitfoundation/bitplatform

Length of output: 2368


🏁 Script executed:

# Check if there's a trigger button for the callout, and look for nav wrapper
cat -n src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor | sed -n '1,80p'

Repository: bitfoundation/bitplatform

Length of output: 4476


🏁 Script executed:

# Check if the callout div has display style or if visibility is controlled elsewhere
cat -n src/BlazorUI/Bit.BlazorUI/Components/Navs/Breadcrumb/BitBreadcrumb.razor | sed -n '144,210p'

Repository: bitfoundation/bitplatform

Length of output: 3902


Remove role="listbox" from the breadcrumb callout.

The overflow callout uses role="listbox", which is semantically incorrect for breadcrumb patterns. Per the WAI-ARIA breadcrumb pattern specification, breadcrumb overflow items should be plain navigation links (or buttons for actions) without special roles like listbox or menu. The current structure—containing <a>, <button>, and <span> elements with aria-current="page"—is semantically sound; simply remove the role="listbox" attribute.

Additionally, ensure the breadcrumb component is wrapped in a <nav> landmark if not already present, as this provides proper semantic context for navigation patterns.

🤖 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/Components/Navs/Breadcrumb/BitBreadcrumb.razor`
around lines 144 - 148, Remove the incorrect ARIA role by deleting
role="listbox" from the callout div in BitBreadcrumb.razor (the element using
`@_calloutId`, Styles?.Callout and Classes?.Callout); keep the rest of the markup
for overflow items intact so links/buttons/spans retain their aria-current
attributes. Also verify the breadcrumb component is rendered inside a <nav>
landmark (or wrap the top-level breadcrumb output in a <nav> if it isn't) to
provide proper navigation semantics.

Comment on lines +171 to +188
private static moveCalloutToBody(calloutId: string, callout: HTMLElement, overlayId: string) {
if (callout.parentElement === document.body) return;

const overlay = overlayId ? document.getElementById(overlayId) : null;

Callouts._calloutOriginalParents.set(calloutId, {
parent: callout.parentElement!,
nextSibling: callout.nextSibling,
overlay: overlay,
overlayParent: overlay?.parentElement ?? null,
overlayNextSibling: overlay?.nextSibling ?? null
});

document.body.appendChild(callout);
if (overlay) {
document.body.appendChild(overlay);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against detached callout elements.

Line 177 uses a non-null assertion (callout.parentElement!) without checking if callout.parentElement is actually non-null. If the callout element is detached from the DOM when moveCalloutToBody is called, this will throw a runtime error.

🛡️ Suggested guard
 private static moveCalloutToBody(calloutId: string, callout: HTMLElement, overlayId: string) {
     if (callout.parentElement === document.body) return;
 
     const overlay = overlayId ? document.getElementById(overlayId) : null;
 
+    if (!callout.parentElement) return;
+
     Callouts._calloutOriginalParents.set(calloutId, {
         parent: callout.parentElement!,
🤖 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/Scripts/Callouts.ts` around lines 171 - 188, The
code uses a non-null assertion on callout.parentElement in moveCalloutToBody
which will throw if the element is detached; update moveCalloutToBody to guard
for a null parentElement by checking callout.parentElement before using it (or
set parent and nextSibling to null when detached) and then populate
Callouts._calloutOriginalParents with those safe values (use calloutId, callout,
overlay and overlayParent/overlayNextSibling as before) so no non-null assertion
is used and no runtime error occurs when callout is detached.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/BlazorUI/Bit.BlazorUI/Scripts/Callouts.ts:230

  • _calloutOriginalParents entries are only removed inside restoreCalloutToOriginalParent. In replaceCurrent, if the previous callout element is no longer in the DOM (e.g., component disposed while open / DOM removed), previousCallout will be null and the map entry for current.calloutId will never be deleted, leaking memory and stale references. Consider always clearing the stored original-parent entry for current.calloutId even when the element can’t be found/restored.
            if (callout.calloutId !== current.calloutId) {
                const previousCallout = document.getElementById(current.calloutId);
                if (previousCallout) {
                    previousCallout.style.display = 'none';
                    Callouts.restoreCalloutToOriginalParent(current.calloutId, previousCallout);
                }

                const overlay = current.overlayId && document.getElementById(current.overlayId);
                overlay && (overlay.style.display = 'none');

Comment on lines +184 to +187
document.body.appendChild(callout);
if (overlay) {
document.body.appendChild(overlay);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Styling issue when using Bit components with Callout

3 participants