fix: demos and overlay styles
chore: overlay documentation fixes
This commit is contained in:
parent
9d31c179b1
commit
0bc604d600
31 changed files with 565 additions and 542 deletions
|
|
@ -17,10 +17,10 @@ export const main = () => html`
|
|||
</style>
|
||||
<lion-dialog>
|
||||
<button slot="invoker">Click me to open dialog</button>
|
||||
<div slot="content" class="dialog">
|
||||
<div slot="content" class="demo-dialog-content">
|
||||
Hello! You can close this dialog here:
|
||||
<button
|
||||
class="close-button"
|
||||
class="demo-dialog-content__close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
|
|
@ -48,16 +48,12 @@ import { LionDialog } from '@lion/ui/dialog.js';
|
|||
import '@lion/ui/define/lion-dialog.js';
|
||||
```
|
||||
|
||||
- Your `slot="content"` node will be moved to the global overlay container during initialization.
|
||||
After, your content node is no longer a child of `lion-dialog`.
|
||||
If you still need to access it from the `lion-dialog` you can do so by using the `._overlayContentNode` property.
|
||||
- To close the overlay from within the content node, you need to dispatch a `close-overlay` event that bubbles.
|
||||
It has to be able to reach the content node.
|
||||
- If you need to traverse shadow boundaries, you will have to add `composed: true` as well, although this is discouraged as a practice.
|
||||
It has to be able to reach the content node (if you need to traverse shadow boundaries, you will have to add `composed: true` as well).
|
||||
|
||||
## Changing the configuration
|
||||
|
||||
You can use the `config` property on the dialog to change the configuration.
|
||||
The documentation of the full config object can be found in the `lion/overlay` package or here in [Overlay System - Configuration](../../fundamentals/systems/overlays/configuration.md).
|
||||
You can use the `.config` property on the dialog to change the configuration.
|
||||
The documentation of the full config object can be found in the `overlays` folder or here in [Overlay System - Configuration](../../fundamentals/systems/overlays/configuration.md).
|
||||
|
||||
The `config` property uses a setter to merge the passed configuration with the current, so you only **overwrite what you pass** when updating `config`.
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
import { css } from 'lit';
|
||||
|
||||
export default css``;
|
||||
|
|
@ -36,4 +36,22 @@ export const demoStyle = css`
|
|||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.demo-dialog-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
background-color: black;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.demo-dialog-content__close-button {
|
||||
color: black;
|
||||
font-size: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -22,61 +22,14 @@ import './src/slots-dialog-content.js';
|
|||
</lion-dialog>
|
||||
```
|
||||
|
||||
## Styling content
|
||||
|
||||
It's not possible to style content from the dialog component. This is because the content slot is moved to the global root node. This is why a custom component should be created and slotted in as the content. This ensures style encapsulation on the dialog content.
|
||||
|
||||
```js preview-story
|
||||
export const stylingContent = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<lion-dialog .config=${{ hidesOnOutsideClick: true, hidesOnEsc: true }}>
|
||||
<button slot="invoker">Styled dialog</button>
|
||||
<styled-dialog-content slot="content"></styled-dialog-content>
|
||||
</lion-dialog>
|
||||
`;
|
||||
```
|
||||
|
||||
## Content with slots
|
||||
|
||||
```js preview-story
|
||||
export const slotsContent = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<lion-dialog .config=${{ hidesOnOutsideClick: true, hidesOnEsc: true }}>
|
||||
<button slot="invoker">Dialog with content with slots</button>
|
||||
<slots-dialog-content slot="content">
|
||||
<p>Some Stuff</p>
|
||||
<p slot="actions">I am in the actions slot</p>
|
||||
</slots-dialog-content>
|
||||
</lion-dialog>
|
||||
`;
|
||||
```
|
||||
|
||||
## Close overlay from component slotted as content
|
||||
|
||||
The overlay cannot be closed by dispatching the `close-overlay` from a button in a styled component that is slotted in as content, because it will not cross the shadow boundary of the component. A method should be created that will dispatch the `close-overlay` event from the component.
|
||||
|
||||
```js preview-story
|
||||
export const closeOverlayFromComponent = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<lion-dialog .config=${{ hidesOnOutsideClick: true, hidesOnEsc: true }}>
|
||||
<button slot="invoker">Styled dialog</button>
|
||||
<styled-dialog-content slot="content"></styled-dialog-content>
|
||||
</lion-dialog>
|
||||
`;
|
||||
```
|
||||
|
||||
## Placement overrides
|
||||
|
||||
```js preview-story
|
||||
export const placementOverrides = () => {
|
||||
const dialog = placement => html`
|
||||
<lion-dialog .config="${{ viewportConfig: { placement } }}">
|
||||
const dialog = placement => {
|
||||
const cfg = { viewportConfig: { placement } };
|
||||
return html`
|
||||
<lion-dialog .config="${cfg}">
|
||||
<button slot="invoker">Dialog ${placement}</button>
|
||||
<div slot="content" class="dialog demo-box">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -89,6 +42,7 @@ export const placementOverrides = () => {
|
|||
</div>
|
||||
</lion-dialog>
|
||||
`;
|
||||
};
|
||||
return html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
|
|
@ -116,23 +70,24 @@ Configuration passed to `config` property:
|
|||
No backdrop, hides on escape, prevents scrolling while opened, and focuses the body when hiding.
|
||||
|
||||
```js preview-story
|
||||
export const otherOverrides = () => html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<lion-dialog
|
||||
.config=${{
|
||||
export const otherOverrides = () => {
|
||||
const cfg = {
|
||||
hasBackdrop: false,
|
||||
hidesOnEscape: true,
|
||||
preventsScroll: true,
|
||||
elementToFocusAfterHide: document.body,
|
||||
}}
|
||||
>
|
||||
};
|
||||
|
||||
return html`
|
||||
<style>
|
||||
${demoStyle}
|
||||
</style>
|
||||
<lion-dialog .config="${cfg}">
|
||||
<button slot="invoker">Click me to open dialog</button>
|
||||
<div slot="content" class="dialog">
|
||||
<div slot="content" class="demo-dialog-content">
|
||||
Hello! You can close this dialog here:
|
||||
<button
|
||||
class="close-button"
|
||||
class="demo-dialog-content__close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
|
|
@ -140,6 +95,7 @@ export const otherOverrides = () => html`
|
|||
</div>
|
||||
</lion-dialog>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
||||
Configuration passed to `config` property:
|
||||
|
|
|
|||
116
docs/fundamentals/systems/overlays/_configuration-positioning.md
Normal file
116
docs/fundamentals/systems/overlays/_configuration-positioning.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
|
||||
# Systems >> Overlays >> Configuration >> Positioning ||40
|
||||
|
||||
```js script
|
||||
import { html, render, LitElement } from '@mdjs/mdjs-preview';
|
||||
import { ref, createRef } from 'lit/directives/ref.js';
|
||||
import './assets/demo-el-using-overlaymixin.mjs';
|
||||
import './assets/applyDemoOverlayStyles.mjs';
|
||||
import './assets/demo-overlay-positioning.mjs';
|
||||
```
|
||||
|
||||
Overlays can have two different placement modes: relative to their anchor element and relative to the viewport.
|
||||
Depending on screen size and viewing device, one placement mode might be suited better than the other.
|
||||
|
||||
> Note that, the placementMode option has the values 'local' (anchor) and 'global' (viewport). These refer to their
|
||||
> legacy position in dom (global overlays were put in the body of the page). Since overlays are built with the native `<dialog>` element,
|
||||
> no content is moved around anymore, so their names are a bit less intuitive.
|
||||
|
||||
## Relative to anchor
|
||||
|
||||
An anchor is usually the invoker button, it can also be a non interactive reference element.
|
||||
Anchor placement uses Popper under the hood. It supports 9 positions:
|
||||
`top-start`, `top`, `top-end`, `right-start`, `right`, `right-end`, `bottom-start`, `bottom`, `bottom-end`, `left-start`,`left`,`left-end`
|
||||
|
||||
```js story
|
||||
export const localPositioning = () => html`<demo-overlay-positioning></demo-overlay-positioning>`;
|
||||
```
|
||||
|
||||
## Relative to viewport
|
||||
|
||||
Viewport placement uses the flexbox layout mode, leveraging the best browser capabilities when
|
||||
the content or screen size updates.
|
||||
Supported modes:
|
||||
`center`, `top-left`, `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`
|
||||
|
||||
```js story
|
||||
export const globalPositioning = () =>
|
||||
html`<demo-overlay-positioning
|
||||
placement-mode="global"
|
||||
simulate-viewport
|
||||
></demo-overlay-positioning>`;
|
||||
```
|
||||
|
||||
## placementMode
|
||||
|
||||
The `placementMode` property determines the positioning of the `contentNode`:
|
||||
|
||||
- next to its reference node: `local`
|
||||
- relative to the viewport: `global`
|
||||
|
||||
### Local
|
||||
|
||||
<!-- By default, the [`referenceNode`](./configuration-elements#referencenode) is the [invokerNode](/configuration-elements#invokernode). -->
|
||||
|
||||
```js story
|
||||
export const placementLocal = () => {
|
||||
const placementModeLocalConfig = { placementMode: 'local' };
|
||||
return html`
|
||||
<demo-el-using-overlaymixin .config=${placementModeLocalConfig}>
|
||||
<button slot="invoker">Click me to open the local overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
||||
### Global
|
||||
|
||||
```js story
|
||||
export const placementGlobal = () => {
|
||||
const placementModeGlobalConfig = { placementMode: 'global' };
|
||||
return html`
|
||||
<demo-el-using-overlaymixin .config=${placementModeGlobalConfig}>
|
||||
<button slot="invoker">Click me to open the global overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
||||
## popperConfig
|
||||
|
||||
/** Viewport configuration. Will be used when placementMode is 'global' \*/
|
||||
viewportConfig?: ViewportConfig;
|
||||
/** Hides other overlays when multiple are opened (currently exclusive to globalOverlayController) _/
|
||||
isBlocking?: boolean;
|
||||
/\*\* Will align contentNode with referenceNode (invokerNode by default) for local overlays. Usually needed for dropdowns. 'max' will prevent contentNode from exceeding width of referenceNode, 'min' guarantees that contentNode will be at least as wide as referenceNode. 'full' will make sure that the invoker width always is the same. _/
|
||||
inheritsReferenceWidth?: 'max' | 'full' | 'min' | 'none';
|
||||
/\*_ Change the default of 9999 _/
|
||||
zIndex?: number;
|
||||
|
||||
| Prop | Description | Type | | | |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | --- | --- | --- |
|
||||
| placementMode | Determines the positioning anchor (viewport vs invokerNode/referenceNode) | 'global'\|'local' | | | |
|
||||
| viewportConfig | Viewport positioning configuration. Will be used when placementMode is 'global' | {placement: ViewportPlacement} | | | |
|
||||
| popperConfig | Anchor positioning configuration. Will be used when placementMode is 'local' | | | | |
|
||||
| inheritsReferenceWidth | Will align contentNode with referenceNode for local overlays. Usually needed for dropdowns. 'max' will prevent contentNode from exceeding width of referenceNode, 'min' guarantees that contentNode will be at least as wide as referenceNode. 'full' will make sure that the invoker width always is the same. | 'max' \| 'full' \| 'min' \| 'none' | | | |
|
||||
|
|
@ -1,43 +1,17 @@
|
|||
# Systems >> Overlays >> Positioning ||10
|
||||
---
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
|
||||
# Systems >> Overlays >> Edge cases ||10
|
||||
|
||||
```js script
|
||||
import { html, render, LitElement } from '@mdjs/mdjs-preview';
|
||||
import { ref, createRef } from 'lit/directives/ref.js';
|
||||
import './assets/demo-el-using-overlaymixin.mjs';
|
||||
import './assets/applyDemoOverlayStyles.mjs';
|
||||
import './assets/demo-overlay-positioning.mjs';
|
||||
```
|
||||
|
||||
Overlays can have two different placement modes: relative to their anchor element and relative to the viewport.
|
||||
Depending on screen size and viewing device, one placement mode might be suited better than the other.
|
||||
|
||||
> Note that, the placementMode option has the values 'local' (anchor) and 'global' (viewport). These refer to their
|
||||
> legacy position in dom (global overlays were put in the body of the page). Since overlays are built with the native `<dialog>` element,
|
||||
> no content is moved around anymore, so their names are a bit less intuitive.
|
||||
|
||||
## Relative to anchor
|
||||
|
||||
An anchor is usually the invoker button, it can also be a non interactive reference element.
|
||||
Anchor placement uses Popper under the hood. It supports 9 positions:
|
||||
`top-start`, `top`, `top-end`, `right-start`, `right`, `right-end`, `bottom-start`, `bottom`, `bottom-end`, `left-start`,`left`,`left-end`
|
||||
|
||||
```js story
|
||||
export const localPositioning = () => html`<demo-overlay-positioning></demo-overlay-positioning>`;
|
||||
```
|
||||
|
||||
## Relative to viewport
|
||||
|
||||
Viewport placement uses the flexbox layout mode, leveraging the best browser capabilities when
|
||||
the content or screen size updates.
|
||||
Supported modes:
|
||||
`center`, `top-left`, `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`
|
||||
|
||||
```js story
|
||||
export const globalPositioning = () =>
|
||||
html`<demo-overlay-positioning
|
||||
placement-mode="global"
|
||||
simulate-viewport
|
||||
></demo-overlay-positioning>`;
|
||||
```
|
||||
|
||||
## Notorious edge cases
|
||||
|
||||
These edge cases are not so much related to the edges of the viewport or the anchor, but more with the difficulties involved with the dom context of the overlay.
|
||||
|
|
@ -15,7 +15,7 @@ const applyDemoOverlayStyles = () => {
|
|||
`;
|
||||
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.setAttribute('data-demo-global-overlays', '');
|
||||
styleTag.setAttribute('data-demo-overlays', '');
|
||||
styleTag.textContent = demoOverlaysStyle.cssText;
|
||||
document.head.appendChild(styleTag);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { LionButton } from '@lion/ui/button.js';
|
|||
/**
|
||||
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
||||
*/
|
||||
class DemoOverlaySystem extends OverlayMixin(LitElement) {
|
||||
class DemoElUsingOverlayMixin extends OverlayMixin(LitElement) {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_defineOverlayConfig() {
|
||||
return /** @type {OverlayConfig} */ ({
|
||||
|
|
@ -36,12 +36,10 @@ class DemoOverlaySystem extends OverlayMixin(LitElement) {
|
|||
<slot name="invoker"></slot>
|
||||
<slot name="backdrop"></slot>
|
||||
<slot name="content"></slot>
|
||||
|
||||
<div>popup is ${this.opened ? 'opened' : 'closed'}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('demo-overlay-system', DemoOverlaySystem);
|
||||
customElements.define('demo-el-using-overlaymixin', DemoElUsingOverlayMixin);
|
||||
|
||||
class DemoOverlay extends OverlayMixin(LitElement) {
|
||||
static get styles() {
|
||||
|
|
@ -14,6 +14,7 @@ class DemoOverlayBackdrop extends LitElement {
|
|||
height: 100%;
|
||||
background-color: grey;
|
||||
opacity: 0.3;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
:host(.local-overlays__backdrop--visible) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { html, LitElement, css } from 'lit';
|
||||
import { ref, createRef } from 'lit/directives/ref.js';
|
||||
import { OverlayMixin } from '@lion/ui/overlays.js';
|
||||
import './demo-overlay-system.mjs';
|
||||
import './demo-el-using-overlaymixin.mjs';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
||||
|
|
@ -44,8 +44,7 @@ class DemoOverlayEl extends OverlayMixin(LitElement) {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
:host([simulate-viewport])
|
||||
#overlay-content-node-wrapper.global-overlays__overlay-container {
|
||||
:host([simulate-viewport]) #overlay-content-node-wrapper.overlays__overlay-container {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-preview';
|
||||
import './assets/demo-overlay-system.mjs';
|
||||
import './assets/demo-el-using-overlaymixin.mjs';
|
||||
import './assets/applyDemoOverlayStyles.mjs';
|
||||
```
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ The `OverlayMixin` exposes these options via `.config`.
|
|||
|
||||
Either `'local'` or `'global'`.
|
||||
This determines the DOM position of the `contentNode`, either next to the invokerNode,
|
||||
or in the `global-overlays` container at the bottom of the `<body>`.
|
||||
or in the `overlays` container at the bottom of the `<body>`.
|
||||
|
||||
### Local
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ export const placementLocal = () => {
|
|||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
<demo-overlay-system .config=${placementModeLocalConfig}>
|
||||
<demo-el-using-overlaymixin .config=${placementModeLocalConfig}>
|
||||
<button slot="invoker">Click me to open the local overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -39,7 +39,7 @@ export const placementLocal = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -50,7 +50,7 @@ export const placementLocal = () => {
|
|||
export const placementGlobal = () => {
|
||||
const placementModeGlobalConfig = { placementMode: 'global' };
|
||||
return html`
|
||||
<demo-overlay-system .config=${placementModeGlobalConfig}>
|
||||
<demo-el-using-overlaymixin .config=${placementModeGlobalConfig}>
|
||||
<button slot="invoker">Click me to open the global overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -61,7 +61,7 @@ export const placementGlobal = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -91,12 +91,12 @@ export const isTooltip = () => {
|
|||
const tooltipConfig = { placementMode: 'local', isTooltip: true, handlesAccessibility: true };
|
||||
|
||||
return html`
|
||||
<demo-overlay-system id="tooltip" .config=${tooltipConfig}>
|
||||
<demo-el-using-overlaymixin id="tooltip" .config=${tooltipConfig}>
|
||||
<button slot="invoker" @mouseenter=${showTooltip} @mouseleave=${hideTooltip}>
|
||||
Hover me to open the tooltip!
|
||||
</button>
|
||||
<div slot="content" class="demo-overlay">Hello!</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -114,7 +114,7 @@ You use the feature on any type of overlay.
|
|||
export const trapsKeyboardFocus = () => {
|
||||
const trapsKeyboardFocusConfig = { trapsKeyboardFocus: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${trapsKeyboardFocusConfig}>
|
||||
<demo-el-using-overlaymixin .config=${trapsKeyboardFocusConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
<div><a href="#">A focusable anchor</a></div>
|
||||
|
|
@ -127,7 +127,7 @@ export const trapsKeyboardFocus = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -140,7 +140,7 @@ Boolean property. Will allow closing the overlay on ESC key when enabled.
|
|||
export const hidesOnEsc = () => {
|
||||
const hidesOnEscConfig = { hidesOnEsc: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${hidesOnEscConfig}>
|
||||
<demo-el-using-overlaymixin .config=${hidesOnEscConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -151,7 +151,7 @@ export const hidesOnEsc = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -164,7 +164,7 @@ Boolean property. When enabled allows closing the overlay on ESC key, even when
|
|||
export const hidesOnOutsideEsc = () => {
|
||||
const hidesOnEscConfig = { hidesOnOutsideEsc: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${hidesOnEscConfig}>
|
||||
<demo-el-using-overlaymixin .config=${hidesOnEscConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -175,7 +175,7 @@ export const hidesOnOutsideEsc = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -188,7 +188,7 @@ Boolean property. Will allow closing the overlay by clicking outside the `conten
|
|||
export const hidesOnOutsideClick = () => {
|
||||
const hidesOnOutsideClickConfig = { hidesOnOutsideClick: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${hidesOnOutsideClickConfig}>
|
||||
<demo-el-using-overlaymixin .config=${hidesOnOutsideClickConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
<label for="myInput">Clicking this label should not trigger close</label>
|
||||
|
|
@ -200,7 +200,7 @@ export const hidesOnOutsideClick = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -213,9 +213,12 @@ In the example, we focus the body instead of the `invokerNode`.
|
|||
|
||||
```js preview-story
|
||||
export const elementToFocusAfterHide = () => {
|
||||
const elementToFocusAfterHideConfig = { elementToFocusAfterHide: document.body };
|
||||
const btn = document.createElement('button');
|
||||
btn.innerText = 'I should get focus';
|
||||
|
||||
const elementToFocusAfterHideConfig = { elementToFocusAfterHide: btn };
|
||||
return html`
|
||||
<demo-overlay-system .config=${elementToFocusAfterHideConfig}>
|
||||
<demo-el-using-overlaymixin .config=${elementToFocusAfterHideConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -226,7 +229,8 @@ export const elementToFocusAfterHide = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
${btn}
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -239,18 +243,18 @@ Boolean property. When true, will add a backdrop when the overlay is opened.
|
|||
> If this is not what you intend, you can make the overlays not nested, where opening one, closes the other.
|
||||
> Fortunately, we also have a configuration option that simulates that behavior in the next section `isBlocking`.
|
||||
|
||||
The backdrop styling can be configured by targeting the `.global-overlays .global-overlays__backdrop` css selector.
|
||||
The backdrop styling can be configured by targeting the `.overlays .overlays__backdrop` css selector.
|
||||
|
||||
The backdrop animation can be configured by targeting the
|
||||
`.global-overlays .global-overlays__backdrop--animation-in` and
|
||||
`.global-overlays .global-overlays__backdrop--animation-out` css selector.
|
||||
`.overlays .overlays__backdrop--animation-in` and
|
||||
`.overlays .overlays__backdrop--animation-out` css selector.
|
||||
This currently only supports CSS Animations, because it relies on the `animationend` event to add/remove classes.
|
||||
|
||||
```js preview-story
|
||||
export const hasBackdrop = () => {
|
||||
const hasBackdropConfig = { hasBackdrop: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${hasBackdropConfig}>
|
||||
<demo-el-using-overlaymixin .config=${hasBackdropConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -261,7 +265,7 @@ export const hasBackdrop = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -274,14 +278,22 @@ Boolean property. When true, will block other overlays.
|
|||
export const isBlocking = () => {
|
||||
const isBlockingConfig = { hasBackdrop: true, isBlocking: true };
|
||||
return html`
|
||||
<demo-overlay-system>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
<div>
|
||||
<demo-overlay-system .config=${isBlockingConfig}>
|
||||
<button slot="invoker">Click me to open another overlay which is blocking</button>
|
||||
<demo-el-using-overlaymixin>
|
||||
<button slot="invoker">Overlay A: open first</button>
|
||||
<div slot="content" class="demo-overlay" style="width:200px;">
|
||||
This overlay gets closed when overlay B gets opened
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-el-using-overlaymixin>
|
||||
<demo-el-using-overlaymixin .config=${isBlockingConfig}>
|
||||
<button slot="invoker">Overlay B: open second</button>
|
||||
<div slot="content" class="demo-overlay demo-overlay--blocking">
|
||||
Hello! You can close this notification here:
|
||||
Overlay A is hidden... now close me and see overlay A again.
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
|
|
@ -289,17 +301,7 @@ export const isBlocking = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</div>
|
||||
Hello! You can close this notification here:
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -315,7 +317,7 @@ Boolean property. When true, prevents scrolling content that is outside of the `
|
|||
export const preventsScroll = () => {
|
||||
const preventsScrollConfig = { preventsScroll: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${preventsScrollConfig}>
|
||||
<demo-el-using-overlaymixin .config=${preventsScrollConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -326,7 +328,7 @@ export const preventsScroll = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -354,7 +356,7 @@ export const viewportConfig = () => {
|
|||
viewportConfig: { placement: 'bottom-left' },
|
||||
};
|
||||
return html`
|
||||
<demo-overlay-system .config=${viewportConfig}>
|
||||
<demo-el-using-overlaymixin .config=${viewportConfig}>
|
||||
<button slot="invoker">Click me to open the overlay in the bottom left corner!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -365,7 +367,7 @@ export const viewportConfig = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -434,7 +436,7 @@ export const popperConfig = () => {
|
|||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
<demo-overlay-system .config=${popperConfig}>
|
||||
<demo-el-using-overlaymixin .config=${popperConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -445,7 +447,7 @@ export const popperConfig = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
# Systems >> Overlays >> Form Integrations ||60
|
||||
|
||||
```js script
|
||||
import { html } from '@mdjs/mdjs-preview';
|
||||
import '@lion/ui/define/lion-input-datepicker.js';
|
||||
import '@lion/ui/define/lion-listbox.js';
|
||||
import '@lion/ui/define/lion-listbox.js';
|
||||
import '@lion/ui/define/lion-select-rich.js';
|
||||
import './assets/demo-overlay-system.mjs';
|
||||
import './assets/applyDemoOverlayStyles.mjs';
|
||||
```
|
||||
|
||||
## Select Rich
|
||||
|
||||
Opening a Rich Select inside a dialog.
|
||||
|
||||
```js preview-story
|
||||
export const selectRich = () => html`
|
||||
<demo-overlay-system>
|
||||
<button slot="invoker">Open Dialog</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
<h1>Select Rick example</h1>
|
||||
<lion-select-rich name="favoriteColor" label="Favorite color">
|
||||
<lion-option .choiceValue=${'red'}>Red</lion-option>
|
||||
<lion-option .choiceValue=${'hotpink'} checked>Hotpink</lion-option>
|
||||
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
|
||||
</lion-select-rich>
|
||||
<p>
|
||||
You can close this dialog here:
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
`;
|
||||
```
|
||||
|
||||
## Input Datepicker
|
||||
|
||||
Opening an Input Datepicker inside a dialog.
|
||||
|
||||
```js preview-story
|
||||
export const inputDatepicker = () => html`
|
||||
<demo-overlay-system>
|
||||
<button slot="invoker">Open Dialog</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
<h1>Input Datepicker example</h1>
|
||||
<lion-input-datepicker name="date" label="Date"></lion-input-datepicker>
|
||||
<p>
|
||||
You can close this dialog here:
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
`;
|
||||
```
|
||||
|
|
@ -11,7 +11,7 @@ import {
|
|||
withModalDialogConfig,
|
||||
} from '@lion/ui/overlays.js';
|
||||
|
||||
import './assets/demo-overlay-system.mjs';
|
||||
import './assets/demo-el-using-overlaymixin.mjs';
|
||||
import './assets/demo-overlay-backdrop.mjs';
|
||||
import './assets/applyDemoOverlayStyles.mjs';
|
||||
import { ref, createRef } from 'lit/directives/ref.js';
|
||||
|
|
@ -25,7 +25,7 @@ For a detailed rationale, please consult [Rationale](./rationale.md).
|
|||
|
||||
```js preview-story
|
||||
export const main = () => html`
|
||||
<demo-overlay-system>
|
||||
<demo-el-using-overlaymixin>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -33,7 +33,7 @@ export const main = () => html`
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
```
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ render() {
|
|||
or declaratively in your template with the `.config` property
|
||||
|
||||
```html
|
||||
<demo-overlay-system .config=${{ ...withModalDialogConfig() }}>
|
||||
<demo-el-using-overlaymixin .config=${{ ...withModalDialogConfig() }}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -142,7 +142,7 @@ or declaratively in your template with the `.config` property
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
```
|
||||
|
||||
### Backdrop
|
||||
|
|
@ -155,7 +155,7 @@ The easiest way is declarative. This can be achieved by adding a `<slot name="ba
|
|||
export const backdrop = () => {
|
||||
const responsiveModalDialogConfig = { ...withModalDialogConfig() };
|
||||
return html`
|
||||
<demo-overlay-system .config=${responsiveModalDialogConfig}>
|
||||
<demo-el-using-overlaymixin .config=${responsiveModalDialogConfig}>
|
||||
<demo-overlay-backdrop slot="backdrop"></demo-overlay-backdrop>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
|
|
@ -166,7 +166,7 @@ export const backdrop = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -179,7 +179,7 @@ export const backdropImperative = () => {
|
|||
const backdropNode = document.createElement('demo-overlay-backdrop');
|
||||
const responsiveModalDialogConfig = { ...withModalDialogConfig(), backdropNode };
|
||||
return html`
|
||||
<demo-overlay-system .config=${responsiveModalDialogConfig}>
|
||||
<demo-el-using-overlaymixin .config=${responsiveModalDialogConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -189,7 +189,7 @@ export const backdropImperative = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -197,7 +197,7 @@ export const backdropImperative = () => {
|
|||
#### Backdrop animation
|
||||
|
||||
By default our overlay system comes with a backdrop animation.
|
||||
This will add `global-overlays__backdrop--animation-in` and `global-overlays__backdrop--animation-out` classes to your backdrop node.
|
||||
This will add `overlays__backdrop--animation-in` and `overlays__backdrop--animation-out` classes to your backdrop node.
|
||||
If you have `placementMode: 'local'` it will replace those `global` strings in the CSS classes with `local`.
|
||||
|
||||
It expects from you that you act on these classes in your CSS with an animation. For example if you have your own backdrop webcomponent (to encapsulate styles):
|
||||
|
|
@ -234,7 +234,7 @@ Under the hood, the OverlayController listens to `animationend` event, only then
|
|||
export const backdropAnimation = () => {
|
||||
const responsiveModalDialogConfig = { ...withModalDialogConfig() };
|
||||
return html`
|
||||
<demo-overlay-system .config=${responsiveModalDialogConfig}>
|
||||
<demo-el-using-overlaymixin .config=${responsiveModalDialogConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<demo-overlay-backdrop slot="backdrop"></demo-overlay-backdrop>
|
||||
<div slot="content" class="demo-overlay">
|
||||
|
|
@ -245,7 +245,7 @@ export const backdropAnimation = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -264,7 +264,7 @@ Drag the viewport under 600px and open the overlay to see the `withBottomSheetCo
|
|||
export const responsiveSwitching = () => {
|
||||
const responsiveBottomSheetConfig = { ...withBottomSheetConfig() };
|
||||
return html`
|
||||
<demo-overlay-system
|
||||
<demo-el-using-overlaymixin
|
||||
.config=${responsiveBottomSheetConfig}
|
||||
@before-opened=${e => {
|
||||
if (window.innerWidth >= 600) {
|
||||
|
|
@ -283,7 +283,7 @@ export const responsiveSwitching = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -340,7 +340,7 @@ export const responsiveSwitching2 = () => {
|
|||
case 'bottomsheet':
|
||||
return { ...withBottomSheetConfig() };
|
||||
case 'dropdown':
|
||||
return { ...withDropdownConfig(), hasBackdrop: false };
|
||||
return { ...withDropdownConfig(), hasBackdrop: false, inheritsReferenceWidth: true };
|
||||
default:
|
||||
return { ...withModalDialogConfig(), hasBackdrop: true };
|
||||
}
|
||||
|
|
@ -365,7 +365,7 @@ export const responsiveSwitching2 = () => {
|
|||
</select>
|
||||
|
||||
<br />
|
||||
<demo-overlay-system ${ref(overlayRef)} .config=${getConfig(selectRef.value?.value)}>
|
||||
<demo-el-using-overlaymixin ${ref(overlayRef)} .config=${getConfig(selectRef.value?.value)}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -375,7 +375,7 @@ export const responsiveSwitching2 = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -401,7 +401,7 @@ export const openedState = () => {
|
|||
}
|
||||
return html`
|
||||
appState.opened: <span ${ref(myRefs.openedState)}>${appState.opened}</span>
|
||||
<demo-overlay-system
|
||||
<demo-el-using-overlaymixin
|
||||
${ref(myRefs.overlay)}
|
||||
.opened="${appState.opened}"
|
||||
@opened-changed=${onOpenClosed}
|
||||
|
|
@ -415,7 +415,7 @@ export const openedState = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -449,7 +449,7 @@ export const interceptingOpenClose = () => {
|
|||
>
|
||||
${blockOverlay}
|
||||
</button>
|
||||
<demo-overlay-system
|
||||
<demo-el-using-overlaymixin
|
||||
${ref(myRefs.overlay)}
|
||||
@before-closed=${intercept}
|
||||
@before-opened=${intercept}
|
||||
|
|
@ -465,7 +465,7 @@ export const interceptingOpenClose = () => {
|
|||
Hello! You can close this notification here:
|
||||
<button @click=${() => (myRefs.overlay.value.opened = false)}>⨯</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -541,7 +541,7 @@ Below an example is shown with the `isBlocking` option, which makes use of the O
|
|||
export const overlayManager = () => {
|
||||
const hasBackdropConfig = { hasBackdrop: true };
|
||||
return html`
|
||||
<demo-overlay-system .config=${hasBackdropConfig}>
|
||||
<demo-el-using-overlaymixin .config=${hasBackdropConfig}>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -555,8 +555,11 @@ export const overlayManager = () => {
|
|||
Click me to open another overlay which is blocking
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
<demo-overlay-system id="secondOverlay" .config=${{ hasBackdrop: true, isBlocking: true }}>
|
||||
</demo-el-using-overlaymixin>
|
||||
<demo-el-using-overlaymixin
|
||||
id="secondOverlay"
|
||||
.config=${{ hasBackdrop: true, isBlocking: true }}
|
||||
>
|
||||
<div slot="content" class="demo-overlay demo-overlay--second">
|
||||
Hello! You can close this notification here:
|
||||
<button
|
||||
|
|
@ -565,7 +568,7 @@ export const overlayManager = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -580,7 +583,7 @@ Here is the example below
|
|||
export const localBackdrop = () => {
|
||||
const localBackdropConfig = { placementMode: 'local' };
|
||||
return html`
|
||||
<demo-overlay-system .config=${localBackdropConfig}>
|
||||
<demo-el-using-overlaymixin .config=${localBackdropConfig}>
|
||||
<demo-overlay-backdrop slot="backdrop"></demo-overlay-backdrop>
|
||||
<button slot="invoker">Click me to open the overlay!</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
|
|
@ -591,7 +594,7 @@ export const localBackdrop = () => {
|
|||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
@ -604,10 +607,10 @@ It's also possible to compose a nested construction by moving around dom nodes.
|
|||
```js preview-story
|
||||
export const nestedOverlays = () => {
|
||||
return html`
|
||||
<demo-overlay-system .config="${withModalDialogConfig()}">
|
||||
<demo-el-using-overlaymixin .config="${withModalDialogConfig()}">
|
||||
<div slot="content" id="mainContent" class="demo-overlay">
|
||||
open nested overlay:
|
||||
<demo-overlay-system .config="${withModalDialogConfig()}">
|
||||
<demo-el-using-overlaymixin .config="${withModalDialogConfig()}">
|
||||
<div slot="content" id="nestedContent" class="demo-overlay">
|
||||
Nested content
|
||||
<button
|
||||
|
|
@ -617,7 +620,7 @@ export const nestedOverlays = () => {
|
|||
</button>
|
||||
</div>
|
||||
<button slot="invoker" id="nestedInvoker">nested invoker button</button>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
<button
|
||||
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
|
||||
>
|
||||
|
|
@ -625,7 +628,7 @@ export const nestedOverlays = () => {
|
|||
</button>
|
||||
</div>
|
||||
<button slot="invoker" id="mainInvoker">invoker button</button>
|
||||
</demo-overlay-system>
|
||||
</demo-el-using-overlaymixin>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ describe('lion-dialog', () => {
|
|||
// For some reason, globalRootNode is not cleared properly on disconnectedCallback from previous overlay test fixtures...
|
||||
// Not sure why this "bug" happens...
|
||||
beforeEach(() => {
|
||||
const globalRootNode = document.querySelector('.global-overlays');
|
||||
const globalRootNode = document.querySelector('.overlays');
|
||||
if (globalRootNode) {
|
||||
globalRootNode.innerHTML = '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ async function combineLocalizeImports(importPromises) {
|
|||
}
|
||||
return combinedResult;
|
||||
}
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
|
||||
export const localizeNamespaceLoader = /** @param {string} locale */ locale => {
|
||||
switch (locale) {
|
||||
case 'bg-BG':
|
||||
|
|
|
|||
|
|
@ -685,9 +685,7 @@ describe('<lion-input-datepicker>', () => {
|
|||
await myElObj.openCalendar();
|
||||
expect(el.hasArrow).to.be.false;
|
||||
expect(
|
||||
myElObj.overlayController.contentNode.classList.contains(
|
||||
'global-overlays__overlay--bottom-sheet',
|
||||
),
|
||||
myElObj.overlayController.contentNode.classList.contains('overlays__overlay--bottom-sheet'),
|
||||
'Datepicker does not get rendered as bottom sheet',
|
||||
).to.be.false;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { EventTargetShim } from '@lion/ui/core.js';
|
|||
import { adoptStyles } from 'lit';
|
||||
import { overlays } from './singleton.js';
|
||||
import { containFocus } from './utils/contain-focus.js';
|
||||
import { globalOverlaysStyle } from './globalOverlaysStyle.js';
|
||||
import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
||||
|
|
@ -239,7 +239,6 @@ export class OverlayController extends EventTargetShim {
|
|||
viewportConfig: {
|
||||
placement: 'center',
|
||||
},
|
||||
|
||||
zIndex: 9999,
|
||||
};
|
||||
|
||||
|
|
@ -495,7 +494,7 @@ export class OverlayController extends EventTargetShim {
|
|||
* @type {OverlayConfig}
|
||||
* @private
|
||||
*/
|
||||
this.__prevConfig = this.config || {};
|
||||
this.__prevConfig = this.config;
|
||||
|
||||
/** @type {OverlayConfig} */
|
||||
this.config = {
|
||||
|
|
@ -571,10 +570,9 @@ export class OverlayController extends EventTargetShim {
|
|||
if (!OverlayController.popperModule) {
|
||||
OverlayController.popperModule = preloadPopper();
|
||||
}
|
||||
} else {
|
||||
const rootNode = /** @type {ShadowRoot} */ (this.contentWrapperNode.getRootNode());
|
||||
adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), globalOverlaysStyle]);
|
||||
}
|
||||
const rootNode = /** @type {ShadowRoot} */ (this.contentWrapperNode.getRootNode());
|
||||
adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), overlayShadowDomStyle]);
|
||||
this._handleFeatures({ phase: 'init' });
|
||||
}
|
||||
|
||||
|
|
@ -612,10 +610,11 @@ export class OverlayController extends EventTargetShim {
|
|||
// A11y will depend on the type of overlay and is arranged on contentNode level.
|
||||
// Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
|
||||
wrappingDialogElement.setAttribute('role', 'none');
|
||||
wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
|
||||
// N.B. position: fixed is needed to escape out of 'overflow: hidden'
|
||||
// We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
|
||||
// parent stacking context
|
||||
wrappingDialogElement.style.cssText = `display:none; background-image: none; border-style: none; padding: 0; z-index: ${this.config.zIndex};`;
|
||||
wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex};`;
|
||||
this.__wrappingDialogNode = wrappingDialogElement;
|
||||
|
||||
/**
|
||||
|
|
@ -788,16 +787,16 @@ export class OverlayController extends EventTargetShim {
|
|||
*/
|
||||
async _handlePosition({ phase }) {
|
||||
if (this.placementMode === 'global') {
|
||||
const placementClass = `global-overlays__overlay-container--${this.viewportConfig.placement}`;
|
||||
const placementClass = `overlays__overlay-container--${this.viewportConfig.placement}`;
|
||||
|
||||
if (phase === 'show') {
|
||||
this.contentWrapperNode.classList.add('global-overlays__overlay-container');
|
||||
this.contentWrapperNode.classList.add('overlays__overlay-container');
|
||||
this.contentWrapperNode.classList.add(placementClass);
|
||||
this.contentNode.classList.add('global-overlays__overlay');
|
||||
this.contentNode.classList.add('overlays__overlay');
|
||||
} else if (phase === 'hide') {
|
||||
this.contentWrapperNode.classList.remove('global-overlays__overlay-container');
|
||||
this.contentWrapperNode.classList.remove('overlays__overlay-container');
|
||||
this.contentWrapperNode.classList.remove(placementClass);
|
||||
this.contentNode.classList.remove('global-overlays__overlay');
|
||||
this.contentNode.classList.remove('overlays__overlay');
|
||||
}
|
||||
} else if (this.placementMode === 'local' && phase === 'show') {
|
||||
/**
|
||||
|
|
@ -918,7 +917,7 @@ export class OverlayController extends EventTargetShim {
|
|||
if (!backdropNode) {
|
||||
return;
|
||||
}
|
||||
backdropNode.classList.remove(`global-overlays__backdrop--animation-in`);
|
||||
backdropNode.classList.remove(`overlays__backdrop--animation-in`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -939,7 +938,7 @@ export class OverlayController extends EventTargetShim {
|
|||
await this.transitionShow({ backdropNode: this.backdropNode, contentNode: this.contentNode });
|
||||
|
||||
if (showConfig.backdropNode) {
|
||||
showConfig.backdropNode.classList.add(`global-overlays__backdrop--animation-in`);
|
||||
showConfig.backdropNode.classList.add(`overlays__backdrop--animation-in`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1049,7 +1048,7 @@ export class OverlayController extends EventTargetShim {
|
|||
if (!this.config?.backdropNode) {
|
||||
this.__backdropNode = document.createElement('div');
|
||||
// If backdropNode existed in config, styles are applied by implementing party
|
||||
this.__backdropNode.classList.add(`global-overlays__backdrop`);
|
||||
this.__backdropNode.classList.add(`overlays__backdrop`);
|
||||
}
|
||||
// @ts-ignore
|
||||
this.__wrappingDialogNode.prepend(this.backdropNode);
|
||||
|
|
@ -1058,7 +1057,7 @@ export class OverlayController extends EventTargetShim {
|
|||
break;
|
||||
}
|
||||
case 'show':
|
||||
this.backdropNode.classList.add(`global-overlays__backdrop--visible`);
|
||||
this.backdropNode.classList.add(`overlays__backdrop--visible`);
|
||||
this.__hasActiveBackdrop = true;
|
||||
break;
|
||||
case 'hide':
|
||||
|
|
|
|||
|
|
@ -90,10 +90,10 @@ export const OverlayMixinImplementation = superclass =>
|
|||
...this.config, // user provided (e.g. in template)
|
||||
popperConfig: {
|
||||
...(overlayConfig.popperConfig || {}),
|
||||
...(this.config.popperConfig || {}),
|
||||
...(this.config?.popperConfig || {}),
|
||||
modifiers: [
|
||||
...(overlayConfig.popperConfig?.modifiers || []),
|
||||
...(this.config.popperConfig?.modifiers || []),
|
||||
...(this.config?.popperConfig?.modifiers || []),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* @typedef {import('./OverlayController.js').OverlayController} OverlayController
|
||||
*/
|
||||
|
||||
import { globalOverlaysStyle } from './globalOverlaysStyle.js';
|
||||
import { overlayDocumentStyle } from './overlayDocumentStyle.js';
|
||||
|
||||
// Export this as protected var, so that we can easily mock it in tests
|
||||
// TODO: combine with browserDetection of core?
|
||||
|
|
@ -24,8 +24,8 @@ export const _browserDetection = {
|
|||
export class OverlaysManager {
|
||||
static __createGlobalStyleNode() {
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.setAttribute('data-global-overlays', '');
|
||||
styleTag.textContent = /** @type {CSSResult} */ (globalOverlaysStyle).cssText;
|
||||
styleTag.setAttribute('data-overlays', '');
|
||||
styleTag.textContent = /** @type {CSSResult} */ (overlayDocumentStyle).cssText;
|
||||
document.head.appendChild(styleTag);
|
||||
return styleTag;
|
||||
}
|
||||
|
|
@ -184,27 +184,32 @@ export class OverlaysManager {
|
|||
requestToPreventScroll() {
|
||||
const { isIOS, isMacSafari } = _browserDetection;
|
||||
// no check as classList will dedupe it anyways
|
||||
document.body.classList.add('global-overlays-scroll-lock');
|
||||
document.body.classList.add('overlays-scroll-lock');
|
||||
if (isIOS || isMacSafari) {
|
||||
// iOS and safar for mac have issues with overlays with input fields. This is fixed by applying
|
||||
// position: fixed to the body. As a side effect, this will scroll the body to the top.
|
||||
document.body.classList.add('global-overlays-scroll-lock-ios-fix');
|
||||
document.body.classList.add('overlays-scroll-lock-ios-fix');
|
||||
}
|
||||
if (isIOS) {
|
||||
document.documentElement.classList.add('global-overlays-scroll-lock-ios-fix');
|
||||
document.documentElement.classList.add('overlays-scroll-lock-ios-fix');
|
||||
}
|
||||
}
|
||||
|
||||
requestToEnableScroll() {
|
||||
const hasOpenSiblingThatPreventsScroll = this.shownList.some(
|
||||
ctrl => ctrl.preventsScroll === true,
|
||||
);
|
||||
if (hasOpenSiblingThatPreventsScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isIOS, isMacSafari } = _browserDetection;
|
||||
if (!this.shownList.some(ctrl => ctrl.preventsScroll === true)) {
|
||||
document.body.classList.remove('global-overlays-scroll-lock');
|
||||
document.body.classList.remove('overlays-scroll-lock');
|
||||
if (isIOS || isMacSafari) {
|
||||
document.body.classList.remove('global-overlays-scroll-lock-ios-fix');
|
||||
document.body.classList.remove('overlays-scroll-lock-ios-fix');
|
||||
}
|
||||
if (isIOS) {
|
||||
document.documentElement.classList.remove('global-overlays-scroll-lock-ios-fix');
|
||||
}
|
||||
document.documentElement.classList.remove('overlays-scroll-lock-ios-fix');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,146 +0,0 @@
|
|||
import { css } from 'lit';
|
||||
|
||||
export const globalOverlaysStyle = css`
|
||||
.global-overlays {
|
||||
position: fixed;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--top-left {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--top {
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--top-right {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--right {
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--bottom-left {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--bottom {
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--bottom-right {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--left {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.global-overlays__overlay-container--center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.global-overlays__overlay--bottom-sheet {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::slotted(.global-overlays__overlay),
|
||||
.global-overlays__overlay {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.global-overlays__backdrop {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background-color: #333333;
|
||||
filter: opacity(30%);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.global-overlays__backdrop--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.global-overlays__backdrop--animation-in {
|
||||
animation: global-overlays-backdrop-fade-in 300ms;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.global-overlays__backdrop--animation-out {
|
||||
animation: global-overlays-backdrop-fade-out 300ms;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes global-overlays-backdrop-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes global-overlays-backdrop-fade-out {
|
||||
from {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
body > *[inert] {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.global-overlays-scroll-lock {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.global-overlays-scroll-lock-ios-fix {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
html.global-overlays-scroll-lock-ios-fix {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
.global-overlays .global-overlays__backdrop--animation-in {
|
||||
animation: global-overlays-backdrop-fade-in 1ms;
|
||||
}
|
||||
|
||||
.global-overlays .global-overlays__backdrop--animation-out {
|
||||
animation: global-overlays-backdrop-fade-out 1ms;
|
||||
}
|
||||
}
|
||||
`;
|
||||
24
packages/ui/components/overlays/src/overlayDocumentStyle.js
Normal file
24
packages/ui/components/overlays/src/overlayDocumentStyle.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { css } from 'lit';
|
||||
|
||||
export const overlayDocumentStyle = css`
|
||||
body > *[inert] {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.overlays-scroll-lock {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.overlays-scroll-lock-ios-fix {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
html.overlays-scroll-lock-ios-fix {
|
||||
height: 100vh;
|
||||
}
|
||||
`;
|
||||
138
packages/ui/components/overlays/src/overlayShadowDomStyle.js
Normal file
138
packages/ui/components/overlays/src/overlayShadowDomStyle.js
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { css } from 'lit';
|
||||
|
||||
export const overlayShadowDomStyle = css`
|
||||
.overlays {
|
||||
position: fixed;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.overlays__overlay-container {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.overlays__overlay-container::backdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--top-left {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--top {
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--top-right {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--right {
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--bottom-left {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--bottom {
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--bottom-right {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--left {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overlays__overlay-container--center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overlays__overlay--bottom-sheet {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::slotted(.overlays__overlay),
|
||||
.overlays__overlay {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.overlays__backdrop {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background-color: #333333;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlays__backdrop--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.overlays__backdrop--animation-in {
|
||||
animation: overlays-backdrop-fade-in 300ms;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.overlays__backdrop--animation-out {
|
||||
animation: overlays-backdrop-fade-out 300ms;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes overlays-backdrop-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes overlays-backdrop-fade-out {
|
||||
from {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
.overlays .overlays__backdrop--animation-in {
|
||||
animation: overlays-backdrop-fade-in 1ms;
|
||||
}
|
||||
|
||||
.overlays .overlays__backdrop--animation-out {
|
||||
animation: overlays-backdrop-fade-out 1ms;
|
||||
}
|
||||
}
|
||||
|
||||
dialog[data-overlay-outer-wrapper] {
|
||||
background-image: none;
|
||||
border-style: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't want to use pseudo el ::backdrop.
|
||||
* We have our own, that creates more flexibility wrt scrolling etc.
|
||||
*/
|
||||
dialog[data-overlay-outer-wrapper]::backdrop {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
|
@ -96,7 +96,7 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) {
|
|||
)
|
||||
);
|
||||
|
||||
// For now, we skip this test for MacSafari, since the body.global-overlays-scroll-lock-ios-fix
|
||||
// For now, we skip this test for MacSafari, since the body.overlays-scroll-lock-ios-fix
|
||||
// class results in a scrollbar when preventsScroll is true.
|
||||
// However, fully functioning interacive elements (input fields) in the dialog are more important
|
||||
if (_browserDetection.isMacSafari && elWithBigParent._overlayCtrl.preventsScroll) {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ import { simulateTab } from '../src/utils/simulate-tab.js';
|
|||
* @typedef {import('../types/OverlayConfig.js').ViewportPlacement} ViewportPlacement
|
||||
*/
|
||||
|
||||
const wrappingDialogNodeStyle =
|
||||
'display: none; background-image: none; border-style: none; padding: 0px; z-index: 9999;';
|
||||
const wrappingDialogNodeStyle = 'display: none; z-index: 9999;';
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} node
|
||||
|
|
@ -178,8 +177,7 @@ describe('OverlayController', () => {
|
|||
});
|
||||
|
||||
it('succeeds when passing a content node that was created "online"', async () => {
|
||||
const contentNode = document.createElement('div');
|
||||
document.body.appendChild(contentNode);
|
||||
const contentNode = /** @type {HTMLElement} */ (fixtureSync('<div>'));
|
||||
const overlay = new OverlayController({
|
||||
...withLocalTestConfig(),
|
||||
contentNode,
|
||||
|
|
@ -236,7 +234,7 @@ describe('OverlayController', () => {
|
|||
|
||||
// The total dom structure created...
|
||||
expect(el).shadowDom.to.equal(`
|
||||
<dialog open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<div data-id="content-wrapper">
|
||||
<slot name="content">
|
||||
</slot>
|
||||
|
|
@ -263,7 +261,7 @@ describe('OverlayController', () => {
|
|||
|
||||
// The total dom structure created...
|
||||
expect(el).lightDom.to.equal(`
|
||||
<dialog open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<div data-id="content-wrapper">
|
||||
<div id="content">non projected</div>
|
||||
</div>
|
||||
|
|
@ -304,7 +302,7 @@ describe('OverlayController', () => {
|
|||
|
||||
// The total dom structure created...
|
||||
expect(el).shadowDom.to.equal(`
|
||||
<dialog open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<div data-id="content-wrapper">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
|
@ -347,7 +345,7 @@ describe('OverlayController', () => {
|
|||
|
||||
// The total dom structure created...
|
||||
expect(el).shadowDom.to.equal(`
|
||||
<dialog open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<div data-id="content-wrapper">
|
||||
<div id="arrow"></div>
|
||||
<slot name="content"></slot>
|
||||
|
|
@ -414,8 +412,8 @@ describe('OverlayController', () => {
|
|||
// The total dom structure created...
|
||||
expect(el).shadowDom.to.equal(
|
||||
`
|
||||
<dialog open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<div class="global-overlays__backdrop"></div>
|
||||
<dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
|
||||
<div class="overlays__backdrop"></div>
|
||||
<div data-id="content-wrapper">
|
||||
<slot name="content">
|
||||
</slot>
|
||||
|
|
@ -565,6 +563,18 @@ describe('OverlayController', () => {
|
|||
document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
|
||||
expect(ctrl.isShown).to.be.true;
|
||||
});
|
||||
|
||||
it('does not hide when [escape] is pressed with modal <dialog> and "hidesOnEsc" is false', async () => {
|
||||
const ctrl = new OverlayController({
|
||||
...withGlobalTestConfig(),
|
||||
trapsKeyboardFocus: true,
|
||||
hidesOnEsc: false,
|
||||
});
|
||||
await ctrl.show();
|
||||
ctrl.contentNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
|
||||
await aTimeout(0);
|
||||
expect(ctrl.isShown).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('hidesOnOutsideEsc', () => {
|
||||
|
|
@ -929,10 +939,10 @@ describe('OverlayController', () => {
|
|||
});
|
||||
|
||||
await ctrl.show();
|
||||
expect(getComputedStyle(document.body).overflow).to.equal('hidden');
|
||||
expect(Array.from(document.body.classList)).to.contain('overlays-scroll-lock');
|
||||
|
||||
await ctrl.hide();
|
||||
expect(getComputedStyle(document.body).overflow).to.equal('visible');
|
||||
expect(Array.from(document.body.classList)).to.not.contain('overlays-scroll-lock');
|
||||
});
|
||||
|
||||
it('keeps preventing of scrolling when multiple overlays are opened and closed', async () => {
|
||||
|
|
@ -948,7 +958,7 @@ describe('OverlayController', () => {
|
|||
await ctrl0.show();
|
||||
await ctrl1.show();
|
||||
await ctrl1.hide();
|
||||
expect(getComputedStyle(document.body).overflow).to.equal('hidden');
|
||||
expect(Array.from(document.body.classList)).to.contain('overlays-scroll-lock');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -975,7 +985,7 @@ describe('OverlayController', () => {
|
|||
hasBackdrop: true,
|
||||
});
|
||||
await controllerWithBackdrop.show();
|
||||
expect(controllerWithBackdrop.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(controllerWithBackdrop.backdropNode).to.have.class('overlays__backdrop');
|
||||
});
|
||||
|
||||
it('reenables the backdrop when shown/hidden/shown', async () => {
|
||||
|
|
@ -984,10 +994,10 @@ describe('OverlayController', () => {
|
|||
hasBackdrop: true,
|
||||
});
|
||||
await ctrl.show();
|
||||
expect(ctrl.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(ctrl.backdropNode).to.have.class('overlays__backdrop');
|
||||
await ctrl.hide();
|
||||
await ctrl.show();
|
||||
expect(ctrl.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(ctrl.backdropNode).to.have.class('overlays__backdrop');
|
||||
});
|
||||
|
||||
it('adds and stacks backdrops if .hasBackdrop is enabled', async () => {
|
||||
|
|
@ -996,14 +1006,14 @@ describe('OverlayController', () => {
|
|||
hasBackdrop: true,
|
||||
});
|
||||
await ctrl0.show();
|
||||
expect(ctrl0.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(ctrl0.backdropNode).to.have.class('overlays__backdrop');
|
||||
|
||||
const ctrl1 = new OverlayController({
|
||||
...withGlobalTestConfig(),
|
||||
hasBackdrop: false,
|
||||
});
|
||||
await ctrl1.show();
|
||||
expect(ctrl0.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(ctrl0.backdropNode).to.have.class('overlays__backdrop');
|
||||
expect(ctrl1.backdropNode).to.be.undefined;
|
||||
|
||||
const ctrl2 = new OverlayController({
|
||||
|
|
@ -1012,9 +1022,9 @@ describe('OverlayController', () => {
|
|||
});
|
||||
await ctrl2.show();
|
||||
|
||||
expect(ctrl0.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(ctrl0.backdropNode).to.have.class('overlays__backdrop');
|
||||
expect(ctrl1.backdropNode).to.be.undefined;
|
||||
expect(ctrl2.backdropNode).to.have.class('global-overlays__backdrop');
|
||||
expect(ctrl2.backdropNode).to.have.class('overlays__backdrop');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1404,15 +1414,11 @@ describe('OverlayController', () => {
|
|||
});
|
||||
|
||||
ctrl.show();
|
||||
expect(
|
||||
ctrl.contentWrapperNode.classList.contains('global-overlays__overlay-container--center'),
|
||||
);
|
||||
expect(ctrl.contentWrapperNode.classList.contains('overlays__overlay-container--center'));
|
||||
expect(ctrl.isShown).to.be.true;
|
||||
|
||||
ctrl.updateConfig({ viewportConfig: { placement: 'top-right' } });
|
||||
expect(
|
||||
ctrl.contentWrapperNode.classList.contains('global-overlays__overlay-container--top-right'),
|
||||
);
|
||||
expect(ctrl.contentWrapperNode.classList.contains('overlays__overlay-container--top-right'));
|
||||
expect(ctrl.isShown).to.be.true;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ describe('OverlaysManager', () => {
|
|||
});
|
||||
|
||||
it('provides global stylesheet for arrangement of body scroll', () => {
|
||||
expect(document.head.querySelectorAll('[data-global-overlays]').length).to.equal(1);
|
||||
expect(document.head.querySelectorAll('[data-overlays]').length).to.equal(1);
|
||||
});
|
||||
|
||||
it('provides .teardown() for cleanup', () => {
|
||||
expect(document.head.querySelector('[data-global-overlays=""]')).not.be.undefined;
|
||||
expect(document.head.querySelector('[data-overlays=""]')).not.be.undefined;
|
||||
|
||||
mngr.teardown();
|
||||
expect(document.head.querySelector('[data-global-overlays=""]')).be.null;
|
||||
expect(document.head.querySelector('[data-overlays=""]')).be.null;
|
||||
|
||||
// safety check via private access (do not use this)
|
||||
expect(OverlaysManager.__globalStyleNode).to.be.undefined;
|
||||
|
|
@ -122,22 +122,18 @@ describe('OverlaysManager', () => {
|
|||
});
|
||||
|
||||
describe('When initialized with "preventsScroll: true"', () => {
|
||||
it('adds class "global-overlays-scroll-lock-ios-fix" to body and html on iOS', async () => {
|
||||
it('adds class "overlays-scroll-lock-ios-fix" to body and html on iOS', async () => {
|
||||
mockIOS();
|
||||
const dialog = new OverlayController({ ...defaultOptions, preventsScroll: true }, mngr);
|
||||
await dialog.show();
|
||||
expect(Array.from(document.body.classList)).to.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
expect(Array.from(document.body.classList)).to.contain('overlays-scroll-lock-ios-fix');
|
||||
expect(Array.from(document.documentElement.classList)).to.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
'overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
await dialog.hide();
|
||||
expect(Array.from(document.body.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
expect(Array.from(document.body.classList)).to.not.contain('overlays-scroll-lock-ios-fix');
|
||||
expect(Array.from(document.documentElement.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
'overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
|
||||
// When we are not iOS nor MacSafari
|
||||
|
|
@ -146,30 +142,24 @@ describe('OverlaysManager', () => {
|
|||
|
||||
const dialog2 = new OverlayController({ ...defaultOptions, preventsScroll: true }, mngr);
|
||||
await dialog2.show();
|
||||
expect(Array.from(document.body.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
expect(Array.from(document.body.classList)).to.not.contain('overlays-scroll-lock-ios-fix');
|
||||
expect(Array.from(document.documentElement.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
'overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
});
|
||||
|
||||
it('adds class "global-overlays-scroll-lock-ios-fix" to body on MacSafari', async () => {
|
||||
it('adds class "overlays-scroll-lock-ios-fix" to body on MacSafari', async () => {
|
||||
mockMacSafari();
|
||||
const dialog = new OverlayController({ ...defaultOptions, preventsScroll: true }, mngr);
|
||||
await dialog.show();
|
||||
expect(Array.from(document.body.classList)).to.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
expect(Array.from(document.body.classList)).to.contain('overlays-scroll-lock-ios-fix');
|
||||
expect(Array.from(document.documentElement.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
'overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
await dialog.hide();
|
||||
expect(Array.from(document.body.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
expect(Array.from(document.body.classList)).to.not.contain('overlays-scroll-lock-ios-fix');
|
||||
expect(Array.from(document.documentElement.classList)).to.not.contain(
|
||||
'global-overlays-scroll-lock-ios-fix',
|
||||
'overlays-scroll-lock-ios-fix',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,9 +24,8 @@ describe('Global Positioning', () => {
|
|||
...withDefaultGlobalConfig(),
|
||||
});
|
||||
await ctrl.show();
|
||||
expect(
|
||||
ctrl.contentWrapperNode.classList.contains('global-overlays__overlay-container--center'),
|
||||
).to.be.true;
|
||||
expect(ctrl.contentWrapperNode.classList.contains('overlays__overlay-container--center')).to
|
||||
.be.true;
|
||||
});
|
||||
|
||||
it('positions relative to the viewport ', async () => {
|
||||
|
|
@ -51,7 +50,7 @@ describe('Global Positioning', () => {
|
|||
await ctrl.show();
|
||||
expect(
|
||||
ctrl.contentWrapperNode.classList.contains(
|
||||
`global-overlays__overlay-container--${viewportPlacement}`,
|
||||
`overlays__overlay-container--${viewportPlacement}`,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,23 @@
|
|||
import { Options } from '@popperjs/core';
|
||||
|
||||
export interface OverlayConfig {
|
||||
/** Determines the connection point in DOM (body vs next to invoker). */
|
||||
// Positioning
|
||||
|
||||
/** Determines the positioning anchore (viewport vs invokerNode/referenceNode). */
|
||||
placementMode?: 'global' | 'local' | undefined;
|
||||
/** Popper configuration. Will be used when placementMode is 'local' */
|
||||
popperConfig?: Partial<Options>;
|
||||
/** Viewport positioning configuration. Will be used when placementMode is 'global' */
|
||||
viewportConfig?: ViewportConfig;
|
||||
/** Hides other overlays when multiple are opened (currently exclusive to globalOverlayController) */
|
||||
isBlocking?: boolean;
|
||||
/** Will align contentNode with referenceNode (invokerNode by default) for local overlays. Usually needed for dropdowns. 'max' will prevent contentNode from exceeding width of referenceNode, 'min' guarantees that contentNode will be at least as wide as referenceNode. 'full' will make sure that the invoker width always is the same. */
|
||||
inheritsReferenceWidth?: 'max' | 'full' | 'min' | 'none';
|
||||
/** Change the default of 9999 */
|
||||
zIndex?: number;
|
||||
|
||||
// Elements
|
||||
|
||||
/** The interactive element (usually a button) invoking the dialog or tooltip */
|
||||
invokerNode?: HTMLElement;
|
||||
/** The element that is used to position the overlay content relative to. Usually, this is the same element as invokerNode. Should only be provided when invokerNode should not be positioned against */
|
||||
|
|
@ -15,10 +30,14 @@ export interface OverlayConfig {
|
|||
backdropNode?: HTMLElement;
|
||||
/** The element that should be called `.focus()` on after dialog closes */
|
||||
elementToFocusAfterHide?: HTMLElement;
|
||||
|
||||
// Backdrop
|
||||
|
||||
/** Whether it should have a backdrop (currently exclusive to globalOverlayController) */
|
||||
hasBackdrop?: boolean;
|
||||
/** Hides other overlays when multiple are opened (currently exclusive to globalOverlayController) */
|
||||
isBlocking?: boolean;
|
||||
|
||||
// User interaction
|
||||
|
||||
/** Prevents scrolling body content when overlay opened (currently exclusive to globalOverlayController) */
|
||||
preventsScroll?: boolean;
|
||||
/** Rotates tab, implicitly set when 'isModal' */
|
||||
|
|
@ -29,8 +48,9 @@ export interface OverlayConfig {
|
|||
hidesOnOutsideClick?: boolean;
|
||||
/** Hides the overlay when pressing esc, even when contentNode has no focus */
|
||||
hidesOnOutsideEsc?: boolean;
|
||||
/** Will align contentNode with referenceNode (invokerNode by default) for local overlays. Usually needed for dropdowns. 'max' will prevent contentNode from exceeding width of referenceNode, 'min' guarantees that contentNode will be at least as wide as referenceNode. 'full' will make sure that the invoker width always is the same. */
|
||||
inheritsReferenceWidth?: 'max' | 'full' | 'min' | 'none';
|
||||
|
||||
// Accessibility
|
||||
|
||||
/**
|
||||
* For non `isTooltip`:
|
||||
* - sets aria-expanded="true/false" and aria-haspopup="true" on invokerNode
|
||||
|
|
@ -42,17 +62,13 @@ export interface OverlayConfig {
|
|||
* - sets role="tooltip" and aria-labelledby/aria-describedby on the content
|
||||
*/
|
||||
handlesAccessibility?: boolean;
|
||||
|
||||
// Tooltip
|
||||
|
||||
/** Has a totally different interaction- and accessibility pattern from all other overlays. Will behave as role="tooltip" element instead of a role="dialog" element */
|
||||
isTooltip?: boolean;
|
||||
/** By default, the tooltip content is a 'description' for the invoker (uses aria-describedby) Setting this property to 'label' makes the content function as a label (via aria-labelledby) */
|
||||
invokerRelation?: 'label' | 'description';
|
||||
/** Popper configuration. Will be used when placementMode is 'local' */
|
||||
popperConfig?: Partial<Options>;
|
||||
/** Viewport configuration. Will be used when placementMode is 'global' */
|
||||
viewportConfig?: ViewportConfig;
|
||||
|
||||
/** Change the default of 9999 */
|
||||
zIndex?: number;
|
||||
|
||||
/** render a div instead of dialog */
|
||||
_noDialogEl?: Boolean;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const applyDemoOverlayStyles = () => {
|
|||
`;
|
||||
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.setAttribute('data-demo-global-overlays', '');
|
||||
styleTag.setAttribute('data-demo-overlays', '');
|
||||
styleTag.textContent = demoOverlaysStyle.cssText;
|
||||
document.head.appendChild(styleTag);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { html, LitElement } from 'lit';
|
|||
import { OverlayMixin } from '@lion/ui/overlays.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
||||
* @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
|
||||
*/
|
||||
class DemoOverlaySystem extends OverlayMixin(LitElement) {
|
||||
class DemoElUsingOverlayMixin extends OverlayMixin(LitElement) {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_defineOverlayConfig() {
|
||||
return /** @type {OverlayConfig} */ ({
|
||||
|
|
@ -35,8 +35,7 @@ class DemoOverlaySystem extends OverlayMixin(LitElement) {
|
|||
<div id="overlay-content-node-wrapper">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
<div>popup is ${this.opened ? 'opened' : 'closed'}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('demo-overlay-system', DemoOverlaySystem);
|
||||
customElements.define('demo-el-using-overlaymixin', DemoElUsingOverlayMixin);
|
||||
|
|
@ -13,6 +13,7 @@ class DemoOverlayBackdrop extends LitElement {
|
|||
height: 100%;
|
||||
background-color: grey;
|
||||
opacity: 0.3;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
:host(.local-overlays__backdrop--visible) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export { OverlaysManager } from '../components/overlays/src/OverlaysManager.js';
|
||||
|
||||
export { globalOverlaysStyle } from '../components/overlays/src/globalOverlaysStyle.js';
|
||||
export { OverlayController } from '../components/overlays/src/OverlayController.js';
|
||||
export { OverlayMixin } from '../components/overlays/src/OverlayMixin.js';
|
||||
export { ArrowMixin } from '../components/overlays/src/ArrowMixin.js';
|
||||
|
|
|
|||
Loading…
Reference in a new issue