Merge pull request #73 from ing-bank/chore/overlayOverview

chore(overlays): outline doc for all overlay occurrences
This commit is contained in:
Thijs Louisse 2019-06-24 10:59:18 +02:00 committed by GitHub
commit aa30efcf75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 522 additions and 30 deletions

View file

@ -5,16 +5,17 @@
Supports different types of overlays like dialogs, toasts, tooltips, dropdown, etc...
Manages their position on the screen relative to other elements, including other overlays.
## Overlays manager
This is a global singleton needed to manage positions of multiple dialogs next to each other on the entire page.
It does the job automatically, but you need to add every newly created overlay to it using the code provided below:
## Features
- [**Overlays Manager**](./docs/OverlaysManager.md), a global repository keeping track of all different types of overlays.
- [**Overlays Occurrences**](./docs/OverlayOccurrences.md), outline of all possible occurrences of overlays. Divided into two main types:
- [**Global Overlay Controller**](./docs/GlobalOverlayController.md), controller for overlays relative to the viewport.
- [**Local Overlay Controller**](./docs/LocalOverlayController.md), controller for overlays positioned next to invokers they are related to.
## How to use
### Installation
```sh
npm i --save @lion/ajax
npm i --save @lion/overlays
```
### Example
@ -29,28 +30,3 @@ const myCtrl = overlays.add(
// name OverlayTypeController is for illustration purpose only
// please read below about existing classes for different types of overlays
```
## GlobalOverlayController
This is a base class for different global overlays (e.g. a dialog) - the ones positioned relatively to the viewport.
You should not use this controller directly unless you want to create a unique type of global overlays which is not supported out of the box.
All supported types of global overlays are described below.
### ModalDialogController
```js
import { ModalDialogController } from '@lion/overlays';
```
This is an extension of GlobalOverlayController configured to create accessible modal dialogs.
## LocalOverlayController
This is a base class for different local overlays (e.g. a tooltip) - the ones positioned next to invokers they are related to.
You should not use this controller directly unless you want to create a unique type of local overlays which is not supported out of the box.
All supported types of local overlays are described below.
This is currently WIP.
Stay tuned for updates on new types of overlays.

View file

@ -0,0 +1,31 @@
# GlobalOverlayController
This is a base class for different global overlays (e.g. a dialog, see [Overlay Occurrences](./OverlayOccurrences.md) - the ones positioned relatively to the viewport.
You should not use this controller directly unless you want to create a unique type of global overlays which is not supported out of the box.
All supported types of global overlays are described below.
## How to use
### Installation
```sh
npm i --save @lion/overlays
```
### Example
```js
import { overlays } from '@lion/overlays';
const myCtrl = overlays.add(
new GlobalOverlayController({
/* options */
})
);
```
### ModalDialogController
A specific extension of GlobalOverlayController configured to create accessible modal dialogs.
```js
import { ModalDialogController } from '@lion/overlays';
```

View file

@ -0,0 +1,28 @@
# LocalOverlayController
This is a base class for different local overlays (e.g. a [tooltip](../../tooltip/), see [Overlay System Implementation](./OverlaySystemImplementation.md) - the ones positioned next to invokers they are related to.
You should not use this controller directly unless you want to create a unique type of local overlays which is not supported out of the box.
All supported types of local overlays are described below.
## How to use
### Installation
```sh
npm i --save @lion/overlays
```
### Example
```js
import { overlays } from '@lion/overlays';
const myCtrl = overlays.add(
new LocalOverlayController({
/* options */
})
);
```
This is currently WIP.
Stay tuned for updates on new types of overlays.

View file

@ -0,0 +1,240 @@
# Overlay System: Implementation
This document provides an outline of all possible occurrences of overlays found in applications in
general and thus provided by Lion.
For all concepts referred to in this document, please read [Overlay System Scope](./OverlaySystemScope.md).
## Local and global overlay controllers
Currently, we have a global and a local overlay controller, as two separate entities.
Based on provided config, they handle all positioning logic, accessibility and interaction patterns.
All of their configuration options will be described below as part of the _Responsive overlay_ section.
### Connection points and placement contexts
It's currently not clear where the border between global and local overlays lie. They seem to be
separated based on their 'dom connection point' (body vs 'page cursor'(usually invoker sibling)).
However, there is no required relationship here: we can create a modal dialog from
local context('page cursor') as well.
Only, we would have a few concerns when creating global overlays from a local connection point:
- Accessibility will be harder to implement. When wai-aria 1.0 needs to be supported, all siblings
need to have aria-hidden="true" and all parents role="presentation". Not always straightforward
in shadow dom. If we only need to support wai-aria 1.1, we could use aria-modal="true" on the
element with role="dialog". (we basically need to test our supported browsers and screen readers
for compatibility with aria-modal).
- Stacking context need to be managed: the whole 'z-index chain' should win (it's a battle between
parents in the hierarchy). This would require some complex code to cover all edge cases.
- Side effects of parents adding transforms or clipping become a risk. This is hard to detect and
'counter'.
When the dom connection point is 'body', content projection will not work, but a template that
can be rendered without being dependent on its context will be required.
There usually also is a correllation with their relative positioning context: invoker/other
relative element for local(tooltip/popover/dropdown) vs. 'window/viewport level' for global
(dialog/toast/sheet).
For responsive overlays (see below for an elaborate explanation), we need to switch from global to
local. When we switch the dom connection point, (think of rotating a mobile or tablet), we
will loose the current focus, which can be an a11y concern. This can eventually be 'catched' by
syncing the activeElement (we would only loose the screenreader active element (for instance,
focused cell in table mode))
For maximum flexibility, it should be up to the developer to decide how overlays should be rendered,
per instance of an overlay.
## Responsive overlay
Based on screen size, we might want to switch the appearance of an overlay.
For instance: an application menu can be displayed as a dropdown on desktop,
but as a bottom sheet on mobile.
Similarly, a dialog can be displayed as a popover on desktop, but as a (global) dialog on mobile.
To implement such a flexible overlay, we need an 'umbrella' layer that allows for switching between
different configuration options, also between the connection point in dom (global and local).
Luckily, interfaces of Global and OverlayControllers are very similar.
Therefore we can make a wrapping ResponsiveOverlayController.
### Configuration options for local and global overlays
In total, we should end up with configuration options as depicted below, for all possible overlays.
All boolean flags default to 'false'.
Some options are mutually exclusive, in which case their dependent options and requirement will be
mentioned.
Note: a more generic and precise term for all mentionings of `invoker` below would actually be
`relative positioning element`.
```
- {Element} elementToFocusAfterHide - the element that should be called `.focus()` on after dialog closes
- {Boolean} hasBackdrop - whether it should have a backdrop (currently exclusive to globalOverlayController)
- {Boolean} isBlocking - hides other overlays when mutiple are opened (currently exclusive to globalOverlayController)
- {Boolean} preventsScroll - prevents scrolling body content when overlay opened (currently exclusive to globalOverlayController)
- {Boolean} trapsKeyboardFocus - rotates tab, implicitly set when 'isModal'
- {Boolean} hidesOnEsc - hides the overlay when pressing [esc]
- {Boolean} hidesOnOutsideClick - hides the overlay when clicking next to it, exluding invoker. (currently exclusive to localOverlayController)
- {String} cssPosition - 'absolute' or 'fixed'. TODO: choose name that cannot be mistaken for placement like cssPosition or positioningTechnique: https://github.com/ing-bank/lion/pull/61
- {TemplateResult} contentTemplate
- {TemplateResult} invokerTemplate (currently exclusive to LocalOverlayController)
- {Element} invokerNode (currently exclusive to LocalOverlayController)
- {Element} contentNode (currently exclusive to LocalOverlayController)
```
These options are suggested to be added to the current ones:
```
- {Boolean} isModal - sets aria-modal and/or aria-hidden="true" on siblings
- {Boolean} isGlobal - determines the connection point in DOM (body vs handled by user) TODO: rename to renderToBody?
- {Boolean} isTooltip - has a totally different interaction - and accessibility pattern from all
other overlays, so needed for internals.
- {Boolean} handlesUserInteraction - sets toggle on click, or hover when `isTooltip`
- {Boolean} handlesAccessibility -
- For non `isTooltip`:
- sets aria-expanded="true/false" and aria-haspopup="true" on invokerNode
- sets aria-controls on invokerNode
- returns focus to invokerNode on hide
- sets focus to overlay content(?)
- For `isTooltip`:
- sets role="tooltip" and aria-labelledby/aria-describedby on the content
- {Object} placementConfig
- {String} placement - vertical/horizontal position to be supplied to `managePosition`. See https://github.com/ing-bank/lion/pull/61 for current api. Consists of a primary part (where the overlay is located relative from invoker) and secondary alignnment part (how the overlay 'snaps' to the perpendicular boundary of the invoker), separated via '-'.
- primary : 'bottom' | 'top' | 'left' | 'right' | 'over' (this means the overlay will be positioned on top of the invoker. Think for instance of a select dropdown that opens a selected option on top of the invoker (default behavior of `<select>` on iOS))
- secondary : 'start' | 'end' | 'fill' (occupies width of invoker) | 'middle' (implicit option that will be choosen by default when none of the previous are specified)
- all other configuration as described by [Popper.js](https://popper.js.org/)
```
What we should think about more properly is a global placement option (positioned relative to window instead of invoker)
```
// TODO: namings very much under construction (we should also reconsider 'placement' names, see: https://github.com/ing-bank/lion/pull/61)
// Something like the drawing Joren made: https://github.com/ing-bank/lion/issues/36#issuecomment-491855381
- {String} viewportPlacement - consists of a vertical alignment part and an horizontal alignment part,
separated via '-'
- vertical align : 'center' | 'bottom' | 'top' | 'left' | 'right' | 'fullheight'
- horizontal align: 'middle' | 'start' | 'end' | 'fullwidth'
Examples: 'center-middle' (dialog, alertdialog), 'top-fullwidth' (top sheet)
```
## Controllers/behaviors
Controllers/behaviors provide preconfigured configuration objects for the global/local
overlay controllers.
They provide an imperative and very flexible api for creating overlays and should be used by
Subclassers, inside webcomponents.
#### Dialog
```js
{
isGlobal: true,
isModal: true,
hasBackdrop: true,
preventsScroll: true,
trapsKeyboardFocus: true,
hidesOnEsc: true,
handlesUserInteraction: true,
handlesAccessibility: true,
viewportPlacement: 'center-middle',
}
```
#### Tooltip
```js
{
isTooltip: true,
handlesUserInteraction: true,
handlesAccessibility: true,
}
```
#### Popover
```js
{
handlesUserInteraction: true,
handlesAccessibility: true,
}
```
#### Dropdown
It will be quite common to override placement to 'bottom-fullwidth'.
Also, it would be quite common to add a pointerNode.
```js
{
placement: 'bottom',
handlesUserInteraction: true,
handlesAccessibility: true,
}
```
#### Toast
TODO:
- add an option for role="alertdialog" ?
- add an option for a 'hide timer' and belonging a11y features for this
```js
{
...Dialog,
viewportPlacement: 'top-right', (?)
}
```
#### Sheet (bottom, top, left, right)
```js
{
...Dialog,
viewportPlacement: '{top|bottom|left|right}-fullwidth', (?)
}
```
#### Select
No need for a config, will probably invoke ResponsiveOverlayCtrl and switches
config based on media query from Dropdown to BottomSheet/CenteredDialog
#### Combobox/autocomplete
No need for a config, will probably invoke ResponsiveOverlayCtrl and switches
config based on media query from Dropdown to BottomSheet/CenteredDialog
#### Application menu
No need for cfg, will probably invoke ResponsiveOverlayCtrl and switches
config based on media query from Dropdown to BottomSheet/CenteredDialog
## Web components
Web components provide a declaritive, developer friendly interface with a prewconfigured styling
that fits the Design System and makes it really easy for Application Developers to build
user interfaces.
Web components should use
The ground layers for the webcomponents in Lion are the following:
#### Dialog
Imperative might be better here? We can add a web component later if needed.
#### Tooltip
```html
<lion-tooltip>
<button slot="invoker">hover/focus</button>
<div slot="content">This will be shown</div>
</lion-tooltip>
```
#### Popover
```html
<lion-popover>
<button slot="invoker">click/space/enter</button>
<div slot="content">This will be shown</div>
</lion-popover>
```
#### Dropdown
Like the name suggests, the default placement will be button
```html
<lion-dropdown>
<button slot="invoker">click/space/enter</button>
<ul slot="content">
<li>This</li>
<li>will be</li>
<li>shown</li>
</ul>
</lion-dropdown>
```
#### Toast
Imperative might be better here?
#### Sheet (bottom, top, left, right)
Imperative might be better here?
### Web components implementing generic overlays
#### Select, Combobox/autocomplete, Application menu
Those will be separate web components with a lot of form and a11y logic that will be described
in detail in different sections.
They will imoplement the Overlay configuration as described above under 'Controllers/behaviors'.

View file

@ -0,0 +1,217 @@
# Overlay System: Scope
The goal of this document is to specify the goal and duties of the overlay system, mainly by
identifying all different appearances and types of overlays.
## What is an overlay manager?
An overlay is a visual element that is painted on top of a page, breaking out of the regular
document flow.
An overlay manager is a global repository keeping track of all different types of overlays.
The need for a global housekeeping mainly arises when multiple overlays are opened simultaneously.
As opposed to a single overlay, the overlay manager stores knowledge about:
- whether the scroll behaviour of the body element can be manipulated
- what space is available in the window for drawing new overlays
The manager is in charge of rendering an overlay to the DOM. Therefore, a developer should be able
to control:
- Its physical position (where the dialog is attached). This can either be:
- globally: at root level of the DOM. This guarantees a total control over its painting, since
the stacking context can be controlled from here and interfering parents (that set overflow
values or transforms) cant be apparent. Additionally, making a modal dialog requiring
all surroundings to have aria-hidden="true", will be easier when the overlay is attached on
body level.
- locally: next to the invoking element. This gives advantages for accessibility and
(performant) rendering of an overlay next to its invoker on scroll and window resizes
- Toggling of the shown state of the overlay
- Positioning preferences(for instance bottom-left) and strategies (ordered fallback preferences)
Presentation/styling of the overlay is out of the scope of the manager, except for its positioning
in its context.
Accessibility is usually dependent on the type of overlay, its specific implementation and its
browser/screen reader support (aria 1.0 vs 1.1). We strive for an optimum here by supporting
1.0 as a bare minimum and add 1.1 enrichments on top.
For every overlay, the manager has access to the overlay element and the invoker (and possible
other elements that are needed for focus delegation as described in
https://www.w3.org/TR/wai-aria-practices/#dialog_modal (notes).
## Defining different types of overlays
When browsing through the average ui library, one can encounter multiple names for occurrences of
overlays. Here is a list of names encountered throughout the years:
- dialog
- modal
- popover
- popup
- popdown
- popper
- bubble
- balloon
- dropdown
- dropup
- tooltip
- layover
- overlay
- toast
- snackbar
- sheet (bottom, top, left, right)
- etc..
The problem with most of those terms is their lack of clear definition: what might be considered a
tooltip in UI framework A, can be considered a popover in framework B. What can be called a modal
in framework C, might actually be just a dialog. Etc etc…
### Official specifications
In order to avoid confusion and be as specification compliant as possible, its always a good idea
to consult the W3C. This website shows a full list with specifications of accessible web widgets:
https://www.w3.org/TR/wai-aria-practices/.
A great overview of all widget-, structure- and role relations can be found in the ontology diagram
below:
![rdf_model](/uploads/5aa251bd4a7a1ec36241d20e0af8cbb3/rdf_model.png)
https://www.w3.org/WAI/PF/aria-1.1/rdf_model.svg
Out of all the overlay names mentioned above, we can only identify the dialog and the tooltip as
official roles.
Lets take a closer look at their definitions...
### Dialog
The dialog is described as follows by the W3C:
> “A dialog is a window overlaid on either the primary window or another dialog window. Windows
under a modal dialog are inert. That is, users cannot interact with content outside an active
dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so
it is difficult to discern, and in some implementations, attempts to interact with the inert
content cause the dialog to close.
Like non-modal dialogs, modal dialogs contain their tab sequence. That is, Tab and Shift + Tab do
not move focus outside the dialog. However, unlike most non-modal dialogs, modal dialogs do not
provide means for moving keyboard focus outside the dialog window without closing the dialog.”
- specification: https://www.w3.org/TR/wai-aria-1.1/#dialog
- widget description: https://www.w3.org/TR/wai-aria-practices/#dialog_modal
### Tooltip
According to W3C, a tooltip is described by the following:
> “A tooltip is a popup that displays information related to an element when the element receives
keyboard focus or the mouse hovers over it. It typically appears after a small delay and disappears
when Escape is pressed or on mouse out.
> Tooltip widgets do not receive focus. A hover that contains focusable elements can be made using
a non-modal dialog.”
- specification: https://www.w3.org/TR/wai-aria-1.1/#tooltip
- widget description: https://www.w3.org/TR/wai-aria-practices/#tooltip
What needs to be mentioned is that the W3C taskforce didnt reach consensus yet about the above
tooltip description. A good alternative resource:
https://inclusive-components.design/tooltips-toggletips/
### Dialog vs tooltip
Summarizing, the main differences between dialogs and tooltips are:
- Dialogs have a modal option, tooltips dont
- Dialogs have interactive content, tooltips dont
- Dialogs are opened via regular buttons (click/space/enter), tooltips act on focus/mouseover
### Other roles and concepts
Other roles worth mentioning are *alertdialog* (a specific instance of the dialog for system
alerts), select (an abstract role), *combobox* and *menu*.
Also, the W3C document often refers to *popup*. This term is mentioned in the context of *combobox*,
*listbox*, *grid*, *tree*, *dialog* and *tooltip*. Therefore, one could say it could be a term
*aria-haspopup* attribute needs to be mentioned: it can have values menu, listbox, grid,
tree and dialog.
## Common Overlay Components
In our component library, we want to have the following overlay child components:
- Dialog
- Tooltip
- Popover
- Dropdown
- Toast
- Sheet (bottom, top, left, right)
- Select
- Combobox/autocomplete
- Application menu
### Dialog
The dialog is pretty much the dialog as described in the W3C spec, having the modal option applied
by default.
The flexibility in focus delegation (see https://www.w3.org/TR/wai-aria-practices/#dialog_modal
notes) is not implemented, however.
Addressing these:
- The first focusable element in the content: although delegate this focus management to the
implementing developer is highly preferred, since this is highly dependent on the moment the dialog
content has finished rendering. This is something the overlay manager or dialog widget should not
be aware of in order to provide.a clean and reliable component.
- The focusable element after a close: by default, this is the invoker. For different behaviour, a
reference should be supplied to a more logical element in the particular workflow.
### Tooltip
The tooltip is always invoked on hover and has no interactive content. See
https://inclusive-components.design/tooltips-toggletips/ (the tooltip example, not the toggle tip).
### Popover
The popover looks like a crossover between the dialog and the tooltip. Popovers are invoked on
click. For non interactive content, the https://inclusive-components.design/tooltips-toggletips/
toggletip could be applied.
Whenever there would be a close button present, the non interactiveness wouldnt apply.
An alternative implementation: https://whatsock.com/tsg/Coding%20Arena/Popups/Popup%20(Internal%20Content)/demo.htm
This looks more like a small dialog, except that the page flow is respected (no rotating tab)
### Dropdown
The dropdown is not an official aria-widget and thus cant be tied to a specific role. It exists
in a lot of UI libraries and most of them share these properties:
- Preferred position is down
- When no space at bottom, they show up (in which. Case it behaves a as a dropup)
- Unlike popovers and tooltips, it will never be positioned horizontally
Aliases are popdown, pulldown and many others.
### Select
Implemented as a dropdown listbox with invoker button. Depending on the content of the options,
the child list can either be of type listbox or grid.
See: https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-collapsible.html
### Combobox
Implemented as a dropdown combobox with invoker input. Input is used as search filter
and can contain autocomplete or autosuggest functionality:
See: https://www.w3.org/TR/wai-aria-practices/#combobox
### (Application) menu
Or sometimes called context-menu. Uses a dropdown to position its content.
See: https://www.w3.org/WAI/tutorials/menus/flyout/
Be aware not to use role=“menu”: https://www.w3.org/WAI/tutorials/menus/application-menus/
### Toast
See: https://www.webcomponents.org/element/@polymer/paper-toast. Should probably be implemented as
an alertdialog.
### Sheet
See: https://material.io/design/components/sheets-bottom.html. Should probably be a
global(modal) dialog.