Skip to content

Popover

Popover is a lookless ContentControl whose Content is the trigger and whose PopoverContent is the floating panel; the web family’s separate Trigger/Anchor/Positioner parts collapse onto this one root control. It positions its panel via the shared NaviusAnchoredPopup (a native Popup driven by PlacementMath) and pushes onto the shared OverlayStack on open, registering the popup content as an input root so Escape and outside-press dismissal work from inside the Popup’s own HwndSource.

xmlns:navius="clr-namespace:Navius.Wpf.Primitives.Controls;assembly=Navius.Wpf.Primitives"
<navius:NaviusPopover Title="Share" Side="Bottom">
<navius:NaviusButton Content="Share" />
<navius:NaviusPopover.PopoverContent>
<StackPanel>
<TextBlock Text="Copy the link below." />
<navius:NaviusButton Content="Close"
Command="{x:Static navius:NaviusPopover.CloseCommand}" />
</StackPanel>
</navius:NaviusPopover.PopoverContent>
</navius:NaviusPopover>

No NaviusOverlayLayer is required: Popover hosts its panel through a native Popup (NaviusAnchoredPopup), pushing directly onto OverlayStack.GetFor(window) rather than through the layer-hosted mechanism Dialog/AlertDialog/Drawer use.

Property Type Default From Description
PopoverContent object? null NaviusPopover Content of the popup panel.
Title object? null NaviusPopover Labels the popup; mirrored onto AutomationProperties.Name on the popup content when set.
Description object? null NaviusPopover Describes the popup; mirrored onto AutomationProperties.HelpText on the popup content when set.
Side PlacementSide Bottom NaviusPopover Which side of the trigger the popup is placed against.
Align PlacementAlign Center NaviusPopover Cross-axis alignment relative to the trigger.
SideOffset double 6 NaviusPopover Gap, in DIPs, between the trigger and the popup along Side.
AlignOffset double 0 NaviusPopover Offset, in DIPs, along the cross axis.
Modal bool false NaviusPopover Forwarded to OverlayOptions.Modal (available via OverlaySession.IsModal, e.g. to decide whether to render a backdrop). Does not gate focus-trap behavior in this port: see Web deltas.
IsOpen bool false NaviusPopover Open state. Two-way bindable by default.
Content object - ContentControl The trigger.
Command Description
NaviusPopover.CloseCommand Bind any element inside PopoverContent to close the popover, mirroring the web’s NaviusPopoverClose part. Its CommandBinding is added directly to the popup content root in OnApplyTemplate, not class-registered, because the popup content lives inside a Popup’s own disconnected visual root.
Key Behavior
Escape Closes the popover (CloseOnEscape fixed true when the session is pushed).
Tab / Shift + Tab Cycles focus within the popup content. TrapFocus is pushed true unconditionally (see Web deltas), so this always applies regardless of Modal.

An outside press also closes the popover (CloseOnOutsideClick fixed true when the session is pushed); not a keyboard interaction, but shares the same dismiss-routing path.

NaviusPopover does not override OnCreateAutomationPeer, so it ships no custom automation peer or role="dialog"-equivalent mapping. Automation identity is approximated entirely through AutomationProperties.AutomationId set on the trigger and popup-content template parts, plus Name/HelpText mirrored from Title/Description directly onto the popup content (ApplyAutomationLabels()), rather than through a role- or pattern-bearing peer. The unit suite (PopoverTests.cs) exercises the CloseCommand binding, the always-true focus trap, and the absence of any exposed cancelable dismiss callback.

  • Trigger, Anchor, and Positioner collapse onto the root control: Content is the trigger, PopoverContent/Side/Align/SideOffset/AlignOffset are the positioner. There is no separate NaviusPopoverAnchor part; the trigger is always the position reference.
  • Modal vs. focus trap: the web only engages a focus trap when Modal is true. Navius.Wpf.Primitives.Overlays.OverlayOptions.TrapFocus conflates “move focus into the popup on open” and “cycle Tab within it” into one flag with no way to request the first without the second, so this port always pushes TrapFocus = true regardless of Modal; Modal is still tracked and forwarded to OverlayOptions.Modal, but does not currently gate focus behavior differently. Splitting that flag was out of scope for the Overlays substrate.
  • No custom AutomationPeer: aria-haspopup/aria-expanded/role="dialog" are approximated only via AutomationProperties.AutomationId, not a role/pattern-bearing peer.
  • No arrow glyph in the default template: NaviusAnchoredPopup surfaces ArrowSize/ ArrowOffsetX/ArrowOffsetY (added in a later wave), but NaviusPopover does not currently consume them.
  • No cancelable dismiss callbacks are exposed as public events (OnEscapeKeyDown, OnPointerDownOutside, etc. have no NaviusPopover-level equivalent); dismissal always proceeds once requested.
  • Known residual (M6 audit): clicking the trigger to close an open popover can close-then-reopen. The trigger lives in the window’s own HwndSource while the session’s input root is the popup content in the Popup’s separate HwndSource, so a mouse-down on the trigger is “outside” the session and closes it on mouse-down, then the trigger’s own Click (mouse-up) toggles IsOpen back to true. Confirming this needs real mouse routing (the existing test simulates the click handler directly); any fix touches outside-press routing in the shared OverlayStack, so it is left for the Overlays owner.

The Popover Gallery page rendered at the pinned commit, in each theme.

Popover Gallery page in the light theme

Popover Gallery page in the dark theme

Popover Gallery page in the high contrast theme