How to use this component
The PopoverPrimitive
is a headless component that associates a "toggle" element with a "popover" element (both elements act as containers). "Soft" (hover/focus) or click event listeners can be assigned to the toggle, and when triggered they toggle the visibility of the popover.
When the popover is visible, it can be closed in various ways: toggling via the "soft" or click events, clicking outside of the popover, or via the esc
key.
Under the hood, the component uses the native web Popover API to promote the popover content to the top layer. This solves issues related to stacking contexts and provides "light dismiss" functionality (click outside / esc
key) out of the box.
The primitive also uses the Floating UI third-party library to provide anchoring as well as automatic positioning and collision detection functionality.
The internal logic and APIs of this component are quite complex; it's impossible to describe everything in detail. Below we provide a few basic examples, but if you need more in-depth knowledge of how the primitive can be configured and used, we suggest looking at the source code of the component itself as well as the hds-anchored-position
modifier, which is a custom wrapper around the Floating UI library.
Basic invocation
The basic invocation of this primitive uses three different modifiers (setupPrimitiveContainer
, setupPrimitiveToggle
, and setupPrimitivePopover
) applied to three distinct elements (which can be either HTML elements or Ember components):
<Hds::PopoverPrimitive as |PP|>
<div id="container" {{PP.setupPrimitiveContainer}}>
<button id="toggle" {{PP.setupPrimitiveToggle}}>toggle</button>
<div id="popover" {{PP.setupPrimitivePopover}}>popover content</div>
</div>
</Hds::PopoverPrimitive>
The primitive itself doesn't provide any styling to the container, toggle, popover (and arrow) elements, and doesn't generate any extra HTML beyond that which is yielded to the component itself. It provides only the popover, anchoring, and collision detection functionalities to the elements that the "setup" modifiers are applied to.
Event listeners
The visibility of the popover can be toggled via "soft" event listeners (hover/focus) applied to the toggle element:
<Hds::PopoverPrimitive @enableSoftEvents={{true}} as |PP|>
<div id="container" {{PP.setupPrimitiveContainer}}>
<button id="toggle" {{PP.setupPrimitiveToggle}}>toggle</button>
<div id="popover" {{PP.setupPrimitivePopover}}>popover content</div>
</div>
</Hds::PopoverPrimitive>
Notice: The actual technical events used are mouseEnter/Leave
and focusIn/Out
.
Alternatively, the toggle behaviour can be enabled via "click" events:
<Hds::PopoverPrimitive @enableClickEvents={{true}} as |PP|>
<div id="container" {{PP.setupPrimitiveContainer}}>
<button id="toggle" {{PP.setupPrimitiveToggle}}>toggle</button>
<div id="popover" {{PP.setupPrimitivePopover}}>popover content</div>
</div>
</Hds::PopoverPrimitive>
Content positioning
The popover element can be positioned in relation to the toggle anchor using the placement
argument of the @anchoredPositionOptions
:
<Hds::PopoverPrimitive as |PP|>
<div id="container" {{PP.setupPrimitiveContainer}}>
<button id="toggle" {{PP.setupPrimitiveToggle}}>toggle</button>
<div
id="popover"
{{PP.setupPrimitivePopover anchoredPositionOptions=(hash placement="top-start")}}
>popover content</div>
</div>
</Hds::PopoverPrimitive>
Collision detection
The collision detection logic can be controlled using the enableCollisionDetection
argument of the @anchoredPositionOptions
:
<Hds::PopoverPrimitive as |PP|>
<div id="container" {{PP.setupPrimitiveContainer}}>
<button id="toggle" {{PP.setupPrimitiveToggle}}>toggle</button>
<div
id="popover"
{{PP.setupPrimitivePopover anchoredPositionOptions=(hash enableCollisionDetection=true)}}
>popover content</div>
</div>
</Hds::PopoverPrimitive>
For details about how the collision detection works, refer to the Floating UI > Tutorial.
With an arrow
It is possible to account for an arrow element in the positioning of the popover, if an arrowSelector
(or directly an arrowElement
reference) is provided to the anchoredPositionOptions
:
<Hds::PopoverPrimitive as |PP|>
<div id="container" {{PP.setupPrimitiveContainer}}>
<button id="toggle" {{PP.setupPrimitiveToggle}}>toggle</button>
<div
id="popover"
{{PP.setupPrimitivePopover anchoredPositionOptions=(hash arrowSelector="arrow")}}
>
<div id="arrow" />
popover content
</div>
</div>
</Hds::PopoverPrimitive>
Other anchoredPositionOptions
Other options and configurations can be provided to the popover via the @anchoredPositionOptions
argument. Refer to the Component API section below for more details, and to the code of the hds-anchored-position
modifier for an in-depth understanding of how this modifier works.
Component API
PopoverPrimitive
isOpen
boolean
- true
- false (default)
Notice: in this case, the popover can't be dismissed via
esc
or click outside
until the end user has interacted with it (it's in a "manual" state).
enableSoftEvents
boolean
- true
- false (default)
mouseEnter/Leave
+ focusIn/Out
) to the toggle, to control the visibility of the popover content.
enableClickEvents
boolean
- true
- false (default)
onClick
) to the toggle, to control the visibility of the popover content.
onOpen
function
onClose
function
[PP].setupPrimitiveContainer
modifier
[PP].setupPrimitiveToggle
modifier
⚠️ Important: The HTML element must be a
<button>
for accessibility conformance. If not, the component will throw an error.
[PP].setupPrimitivePopover
modifier
anchoredPositionOptions
object as "named" argument, with different keys corresponding to different options (see below). These options are forwarded to the underlying hds-anchored-position
modifier, and in turn are passed down to the Floating UI library.
anchoredPositionOptions.placement
enum
- top
- top-start
- top-end
- right
- right-start
- right-end
- bottom (default)
- bottom-start
- bottom-end
- left
- left-start
- left-end
Notice: if
@enableCollision
is set, the popover will automatically shift position to remain visible when near the edges of the screen regardless of the starting placement.
anchoredPositionOptions.strategy
enum
- absolute (default)
- fixed
position
property. Since the component uses the native web Popover API which promotes the popover to the top layer, there is no need to use the fixed
position to avoid stacking context conflicts, but we leave the option open for edge cases in which this is needed.
Notice: if the position/strategy is set to
fixed
, the rendering of the popover becomes janky when the page is scrolled.
anchoredPositionOptions.offsetOptions
number|object
- 0 (default)
For details see: Floating UI > Offset > Options.
Notice: These options can be used to control the relative position of the arrow in relation to the toggle.
anchoredPositionOptions.enableCollisionDetection
boolean|string
- true
- false (default)
- flip
- shift
- auto
true
, or a single axes can be chosen by passing either the flip
or shift
values. If set to auto
, it will automatically place the popover in the position where there's more space available, but in this case, it will ignore the placement
value.
For an overview of how collision detection works and is controlled see: Floating UI > Tutorial, Floating UI > Flip, Floating UI > Shift, and Floating UI > autoPlacement.
anchoredPositionOptions.flipOptions
object
- { padding: 8 } (default)
flip
middleware in Floating UI, that controls the automatic repositioning of the popover along its side axis.
For details about how this middleware works (and its options) see: Floating UI > Flip
anchoredPositionOptions.shiftOptions
object
- { padding: 8, limiter: limitShift() } (default)
shift
middleware in Floating UI, that controls the automatic repositioning of the popover along its axis of alignment.
For details about how this middleware works (and its options) see: Floating UI > Shift
anchoredPositionOptions.autoPlacementOptions
object
- { padding: 8 } (default)
autoPlacement
middleware in Floating UI that controls the automatic repositioning of the popover based on the most space available.
For details about how this middleware works (and its options) see: Floating UI > Shift
anchoredPositionOptions.middlewareExtra
array
For details about how these functions work in the context of the library (and how to define custom ones) see: Floating UI > Middleware and Floating UI > Custom middleware.
anchoredPositionOptions.arrowElement
DOM element
For details see: Floating UI > Arrow
anchoredPositionOptions.arrowElement
string
arrowElement
option).
anchoredPositionOptions.arrowPadding
number
¨C80C For details see: Floating UI > Arrow > Padding
[PP].toggleElement
DOM element
[PP].popoverElement
DOM element
[PP].showPopover
function
[PP].hidePopover
function
[PP].togglePopover
function
[PP].isOpen
tracked property
isOpen
.