@clr/angular
Version:
Angular components for Clarity
140 lines (116 loc) • 8.66 kB
Markdown
# ClrPopover
A ClrPopover is a complex unit that contains an anchor element, a content element and an object describing how
they _push_ together. We can think of the anchor and the content elements as _pushing_ together at a specific
point. We describe this point in as simple a way as possible so that we can easily detect viewport violations **and**
also write intelligent rules that describe how to manipulate the content container when the content is outside the
viewport bounds.
The ClrPopover utility organizes and manages a popover trigger and a popover content element with several
directives and services described below. This was developed to solve two issues. In the first Clarity popover solution:
1. Anchors and Content elements were inside relatively positioned containers. this causes clipping issues when
the popover component is projected inside a reletively positioned container with `overflow: scroll` on it - e.g as
happens in the datagrid when there is a height set on it
2. Position was determined by the implementing component and exposed to consumers as an `@Input`. This meant the
consumer had to handle determining when placement was not ideal. e.g close to the browser edge where the content
could be clipped by the viewport etc and change the position.
This implementation addresses those issues in the following way:
1. It uses a structural directive to move the content element directly onto the body element.
2. It implements a new way of describing the default (or consumer preferred) position and adds logic that can detect
and reposition the content on the fly.
## EXAMPLE POPOVER
Components will need to use ClrPopoverPosition to describe the default/preferred position (`contentPosition`).
Optionally they can choose to add the scrollToClose and outsideClickToClose event listeners.
This is what it looks like from the implementing component perspective:
```html
<button class="btn" clrPopoverOpenCloseButton role="button" type="button" [attr.aria-owns]="popoverId" clrPopoverAnchor>
<cds-icon shape="home"></cds-icon> Popover Anchor
</button>
<div
[id]="popoverId"
role="dialog"
cdkTrapFocus
*clrPopoverContent="openState at contentPosition; outsideClickToClose: true; scrollToClose: true"
>
<header class="header-4" role="heading">
Header
<button role="button" class="btn btn-link" clrPopoverCloseButton>
<cds-icon shape="window-close" size="36"></cds-icon>
</button>
</header>
<section role="region">
<!-- body -->
</section>
<footer>
<!-- footer -->
</footer>
</div>
```
Notice that the Popover Anchor element has two directives on it, the clrPopoverOpenCloseButton and the clrPopoverAnchor
directive that tells our popover complex which element is the anchor.
The content div also has two directives on it. First, we use the existing focus trap to make sure the users focus
stays on the popover until they dismiss it. And, the workhorse of the ClrPopover utility the structural
directive \*clrPopoverContent which takes an input for open state, an input that takes an instance of
ClrPopoverPosition **for the content position**
## ARCHITECTURE
### Services
Services are broken up into area of responsibility.
1. **ClrPopoverEventsService**: Events are created, added and removed in this service. Popover event listeners
include escape keypresses, clicking, outside clicks and inside clicks (e.g clicks inside the popover content element).
2. **ClrPopoverPositionService**: This service holds references to the anchor element, the content element and
uses
the popover-position operators to position the content, test it for visibility errors and intelligently change
the position based on the errors found.
3. **ClrPopoverToggleService**: manages the open/close state changes for the popover complex and can take into
account the triggering event that started an open or close change.
### Directives
1. **ClrPopoverAnchor**: This directive marks the element that is used as a popover trigger. It hands an
ElementRef to the events service so it can be used for focus management. The element ref is also used by the
positioning service whenever it needs to calculate the x/y offsets for positioning the content near the anchor onscreen.
2. **ClrPopoverCloseButton**: Should only be used inside popover content containers. It will call the toggleWithEvent
method on the toggle service **and, it will set the focus back on the anchor element**.
3. **ClrPopoverOpenCloseButton**: Handles toggling the popover content open and closed with the popover toggle service.
4. **ClrPopoverContent**: Structural directive that takes several inputs (open state, position object, click to
close feature, and scroll to close feature). Using popover complex services it coordinates creating, adding
positioning and removing the content view from the DOM.
### Enums
1. **ClrAlignment**: this is the description of the where point of contact is on both the anchor and the content
element when they are pushed together. START, MIDDLE, END.
2. **ClrAxis**: This is a description of the orientation for the popover complex and \**the direction that anchor and
content elements push into each other on. For example if ClrAxis is VERTICAL then pushing occurs along the *Y* axis. If
ClrAxis is HORIZONTAL then pushing occurs along the *X\* axis.
3. **ClrSide**: The location that content will orient to BEFORE or AFTER in relation to the anchor. When ClrAxis is
VERTICAL, BEFORE is _above_ the anchor and AFTER is *below the anchor. When ClrAxis is HORIZONTAL, BEFORE is *left*
of the anchor and AFTER is *right\* of the anchor.
4. **ClrViewportViolation**: This is a description of the four possible viewport violations that can occur when
popover content is offscreen. Violations are collected into an array when content positions are evaluated and
used to determine if the default position is valid as well as which position to switch to when there are violations.
### Interfaces
1. **ClrPopoverContentOffset**: A description of the object used to position content. It describes the number of px
content needs to move from the origin (0,0) top left of the viewport along both the x and the y axis.
2. **ClrPopoverPosition**: An object that can describe the relationship between anchor to content positions. It
eliminates any invalid positions for content and based on the enum values it makes the math needed to determine how
viewport violations are handled as simple as possible.
3. **ClrVisibilityCoords**: This is an object used to create a virtual position for content that can be tested
without adding the content to the DOM and then testing it for visibility violations.
### Operators
What are the operators? And why have them?
1. **ClrTransform**: A type that describes a function that can take a ClrPopoverPosition argument and an optional
boolean for forward (I couldn't figure out an elegant way to compose the booleans without this arg too). This
function type returns a ClrPopoverPosition. It enables us to functionally compose descriptive functions that can
perform multiple transformations on a position so that a new ClrPopoverOffset can be calculated for positioning.
2. **flipSides**: A function that takes a position and flips the sides.
3. **flipAxis**: A function that takes a position and flips the axises.
4. **nudgeContent**: a function that takes a position and an optional boolean argument. It nudges its content forward
or backward depending on current position and the optional boolean argument.
backward)
5. **flipSidesAndNudgeContent**: A composed function takes a flip function (could be flipSides or flipAxis) and a
nudge function (could be nudgeContent or nudgeAnchor if that ever gets implemented) and the optional arg for which
way to nudge and returns a ClrPopoverPosition.
6. **align**: An exported function that is used by the ClrPopoverPositionService to calculate ClrPopoverContentOffset
before rendering ClrPopoverContent onscreen. It takes a ClrPopoverPosition object and a ClientRect for both the
anchor and the content.
7. **alignHorizontal**: A private function that the align method may use when calculating offset values. It returns
a number.
8. **alignVertical**: A private function that the align method may use when calculating offset values. It returns
a number.
9. **testVisibility**: An exported function that can take the ClrPopoverContentOffset object and its associated
content ClientRect and test it to see if it will be clipped offscreen.