The page navigation is complete. You may now navigate the page content as you wish.
Skip to main content

PopoverPrimitive

An internal utility component that provides popover, anchoring, and collision detection functionalities.

This component is intended only for internal Helios use. If you need to use it, please contact the Design Systems Team.

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.

For older browsers (in particular Firefox 124 and older) that don't support the Popover API, it uses a Popover Polyfill library to emulate the native behaviour.

Learn more

  • For details about the native web Popover API, see: MDN / Popover API
  • For details about the Floating UI third-party library, see: Floating UI

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>

Important: if you don't apply either @enableSoftEvents or @enableClickEvents the popover will not become visible with any kind of interaction (unless it's rendered already opened via the special @isOpen argument).

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

isOpen boolean
  • true
  • false (default)
Controls if the popover should be rendered initially opened.

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)
Assigns "soft" event listeners (mouseEnter/Leave + focusIn/Out) to the toggle, to control the visibility of the popover content.
enableClickEvents boolean
  • true
  • false (default)
Assigns a click event listener (onClick) to the toggle, to control the visibility of the popover content.
onOpen function
A callback function invoked when the popover is opened (if provided).
onClose function
Provides a callback function invoked when the popover is closed (if provided).
[PP].setupPrimitiveContainer modifier
Provides a modifier that needs to be applied to the container of the toggle and popover elements.
[PP].setupPrimitiveToggle modifier
Provides a modifier that needs to be applied to the toggle element.

⚠️ Important: The HTML element must be a <button> for accessibility conformance. If not, the component will throw an error.
[PP].setupPrimitivePopover modifier
Provides a modifier that needs to be applied to the popover element. It accepts an 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
Placement for the preferred starting position of the popover relative to the toggle element.

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
Controls how the layout of the popover is applied using the CSS 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)
A number that represents the distance between the popover element and the toggle element. An object can also be passed, which enables individually configuring different axes.

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
This property controls whether the popover should automatically adapt its position to remain visible when near the edges of the screen. It can be enabled for both axes by setting it to 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)
The options for the 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)
The options for the 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)
The options for the 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
An array of "middleware" functions to be executed by the Floating UI library.

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
A reference to the DOM element that represents the "arrow" decoration, if it exists, that allows its position to be updated according to the popover position in relation to the toggle anchor.

For details see: Floating UI > Arrow
anchoredPositionOptions.arrowElement string
A DOM selector for the "arrow", if it's not possible to provide a direct reference to the DOM element (internally it's then converted to the arrowElement option).
anchoredPositionOptions.arrowPadding number
The padding between the arrow and the edges of the popover element.
¨C83C For details see: Floating UI > Arrow > Padding
[PP].toggleElement DOM element
Provides a reference to the toggle DOM element element (container).
[PP].arrowElement DOM element
Provides a reference to the arrow DOM element (if provided to the setupPrimitivePopover modifier through the anchoredPositionOptions object).
[PP].popoverElement DOM element
A reference to the popover DOM element (container).
[PP].showPopover function
Hook into this function to programmatically show the popover.
[PP].hidePopover function
Hook into this function to programmatically hide ("close") the popover.
[PP].togglePopover function
Hook into this function to programmatically toggle the visibility of the popover.
[PP].isOpen tracked property
Hook into this tracked property to access the state of isOpen.


Related