@clr/angular
Version:
Angular components for Clarity
113 lines (82 loc) • 6.49 kB
Markdown
# ClrPopover Utilities
The `ClrPopover` implementation is a utility infrastructure based on the Angular Overlay and Portals implementation provided in the Angular CDK. It is not a standalone component, but a set of directives and services used to build Clarity overlay components like **Dropdowns**, **Signposts**, and **Tooltips**.
## Goal
The goal is to provide a straightforward utility within the Clarity library that allows for popover-like elements to use a consistent API. It offloads all positioning, z-indexing, and layering logic to the Angular CDK, which is better equipped to handle complex rendering situations.
## Architecture
At its most basic, the popover utility is implemented with a set of directives and a service to track, manage, and subscribe to state change events.
### Service: `ClrPopoverService`
The **`ClrPopoverService`** acts as the central state manager for a specific popover instance. It is **transient** and must be provided at the component level (e.g., inside `ClrDropdown`, `ClrSignpost`, or `ClrTooltip`) to ensure that each popover maintains its own isolated state.
The service is responsible for:
- **State Management**:
- **`open`**: A boolean property to get or set the current visibility state of the popover. Setting this triggers the `openChange` observable.
- **`position`**: Tracks the current `ClrPopoverPosition` (e.g., `bottom-left`), triggering the `getPositionChange()` stream when updated.
- **`popoverType`**: Defines the type of popover (Tooltip, Signpost, etc.) to determine offset calculations.
- **Event Handling**:
- **`toggleWithEvent(event: any)`**: A helper method used by triggers. It toggles the `open` state, stores the triggering event (to prevent conflicts like "click outside" closing immediately), and prevents default scrolling behavior for arrow keys.
- **`openEvent`**: Stores the specific browser event that triggered the opening of the popover.
- **Focus & Origin Management**:
- **`origin`**: Holds the `FlexibleConnectedPositionStrategyOrigin` — either an `ElementRef` (trigger element) or a `ClrPopoverPoint` (`{ x, y }` coordinate). This is used by the CDK to calculate where the overlay should appear.
- **`originElement`**: Convenience getter that returns the origin as `ElementRef<HTMLElement>` when element-based, or `null` when point-based.
- **`originPoint`**: Convenience getter that returns the origin as `ClrPopoverPoint` when point-based, or `null` when element-based.
- **`closeButtonRef`**: Holds the `ElementRef` of the internal close button (if applicable).
- **`focusOrigin()`**: A utility method to return focus to the origin element (e.g., when the menu closes), ensuring accessibility compliance. No-op for point-based origins.
- **`focusCloseButton()`**: Moves focus to the close button inside the content when appropriate.
- **Reactive Streams**:
- **`openChange`**: Observable that emits whenever the open state changes.
- **`popoverVisible`**: Observable used to coordinate when the DOM is actually ready/visible.
- **`getEventChange()`**: Observable stream of the events that triggered state changes.
### Directives
1. **`[clrPopoverOrigin]`**:
- Used on the element that serves as the reference point for positioning.
- Passes an `ElementRef` to the service as the `origin`, which is used for focus management and positioning calculations.
2. **`*clrPopoverContent`**:
- A structural directive that renders the view and hoists it to an Overlay on the `body` element using a `DomPortal`.
- Accepts configuration arguments for the overlay (open state, position, outside click behavior, scroll behavior).
- Manages "Click Outside" and "Scroll to Close" logic.
3. **`[clrPopoverCloseButton]`**:
- Should only be used inside popover content containers.
- Calls the toggle method on the service and restores focus to the origin element.
4. **`[clrPopoverOpenCloseButton]`**:
- Handles toggling the popover content open and closed via the popover service.
- Typically used on triggers (e.g., the button that opens a menu).
### ClrPopoverContent Inputs
The `*clrPopoverContent` directive is the "workhorse" of the utility. It accepts several inputs to configure behavior:
- **`clrPopoverContent`** (boolean):
- Controls the open/closed state of the popover. When true, the overlay is created and attached; when false, it is detached.
- **`clrPopoverContentAt`** (`ClrPopoverPosition | ConnectedPosition`):
- Defines the preferred position of the content relative to the origin (e.g., `top-right`, `bottom-left`). It sets the `preferredPosition` for the CDK overlay.
- **`clrPopoverContentAvailablePositions`** (`ConnectedPosition[]`):
- A prioritized list of fallback positions the overlay can attempt to use if the preferred position does not fit within the viewport.
- **`clrPopoverContentType`** (`ClrPopoverType`):
- Specifies the type of popover (e.g., `DROPDOWN`, `TOOLTIP`, `SIGNPOST`). This determines specific offsets and behavior variations associated with that type.
- **`clrPopoverContentOutsideClickToClose`** (boolean):
- Determines if clicking outside the popover content should automatically close it. Defaults to `true`.
- **`clrPopoverContentScrollToClose`** (boolean):
- Determines if scrolling the page should automatically close the popover. Defaults to `false`.
## Enums
1. **`ClrPopoverType`**: Describes the popover variation: `SIGNPOST`, `TOOLTIP`, `DROPDOWN`, and `DEFAULT`.
2. **`ClrPopoverPosition`**: Describes the popover position relative to the origin (e.g., `top-left`, `bottom-right`).
3. **`ClrPosition`**: Describes the specific point of contact on both the origin and content elements.
---
## Usage Example
To use the popover utility, a host component must provide the `ClrPopoverService`. Components generally provide a `ClrPopoverPosition` via the `at` input to describe the preferred content position.
```html
<button class="btn" clrPopoverOpenCloseButton clrPopoverOrigin [attr.aria-owns]="popoverId">
<cds-icon shape="home"></cds-icon> Popover Origin
</button>
<div
[id]="popoverId"
role="dialog"
cdkTrapFocus
*clrPopoverContent="open;
at: position; availablePositions: [...]; outsideClickToClose: true; scrollToClose: true"
>
<div role="heading">
Overlay Header
<button class="btn btn-sm btn-outline-neutral btn-icon" clrPopoverCloseButton>
<cds-icon shape="times"></cds-icon>
</button>
</div>
<div>Overlay Content</div>
</div>
```