fix: no longer use overlay templates
This commit is contained in:
parent
c899cf26d2
commit
49974bd2b8
24 changed files with 334 additions and 467 deletions
|
|
@ -34,8 +34,6 @@
|
|||
"@lion/overlays": "^0.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lion/button": "^0.3.43",
|
||||
"@lion/icon": "^0.2.9",
|
||||
"@open-wc/demoing-storybook": "^0.2.0",
|
||||
"@open-wc/testing": "^2.3.4"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,21 +15,18 @@ export class LionDialog extends OverlayMixin(LitElement) {
|
|||
}
|
||||
|
||||
_setupOpenCloseListeners() {
|
||||
this.__close = () => {
|
||||
this.opened = false;
|
||||
};
|
||||
this.__toggle = () => {
|
||||
this.opened = !this.opened;
|
||||
};
|
||||
|
||||
if (this._overlayCtrl.invokerNode) {
|
||||
this._overlayCtrl.invokerNode.addEventListener('click', this.__toggle);
|
||||
if (this._overlayInvokerNode) {
|
||||
this._overlayInvokerNode.addEventListener('click', this.__toggle);
|
||||
}
|
||||
}
|
||||
|
||||
_teardownOpenCloseListeners() {
|
||||
if (this._overlayCtrl.invokerNode) {
|
||||
this._overlayCtrl.invokerNode.removeEventListener('click', this.__toggle);
|
||||
if (this._overlayInvokerNode) {
|
||||
this._overlayInvokerNode.removeEventListener('click', this.__toggle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { storiesOf, html, withKnobs, object } from '@open-wc/demoing-storybook';
|
||||
import { css } from '@lion/core';
|
||||
import '@lion/icon/lion-icon.js';
|
||||
import '@lion/button/lion-button.js';
|
||||
import '../lion-dialog.js';
|
||||
|
||||
const dialogDemoStyle = css`
|
||||
|
|
@ -67,24 +65,25 @@ storiesOf('Overlays Specific WC | Dialog', module)
|
|||
</p>
|
||||
<p>
|
||||
To close your dialog from some action performed inside the content slot, fire a
|
||||
<code>close</code> event.
|
||||
<code>hide</code> event.
|
||||
</p>
|
||||
<p>
|
||||
For the dialog to close, it will need to bubble to the content slot (use
|
||||
<code>bubbles: true</code>. Also <code>composed: true</code> if it needs to traverse shadow
|
||||
boundaries)
|
||||
<code>bubbles: true</code>. If absolutely needed <code>composed: true</code> can be used to
|
||||
traverse shadow boundaries)
|
||||
</p>
|
||||
<p>The demo below demonstrates this</p>
|
||||
<div class="demo-box">
|
||||
<lion-dialog>
|
||||
<lion-button slot="invoker">Dialog</lion-button>
|
||||
<button slot="invoker">Dialog</button>
|
||||
<div slot="content" class="dialog">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('dialog-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-dialog>
|
||||
</div>
|
||||
|
|
@ -93,14 +92,15 @@ storiesOf('Overlays Specific WC | Dialog', module)
|
|||
.add('Custom configuration', () => {
|
||||
const dialog = placement => html`
|
||||
<lion-dialog .config=${{ viewportConfig: { placement } }}>
|
||||
<lion-button slot="invoker">Dialog ${placement}</lion-button>
|
||||
<button slot="invoker">Dialog ${placement}</button>
|
||||
<div slot="content" class="dialog">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('dialog-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-dialog>
|
||||
`;
|
||||
|
|
@ -118,14 +118,15 @@ storiesOf('Overlays Specific WC | Dialog', module)
|
|||
.add('Toggle placement with knobs', () => {
|
||||
const dialog = html`
|
||||
<lion-dialog .config=${object('config', { viewportConfig: { placement: 'center' } })}>
|
||||
<lion-button slot="invoker">Dialog</lion-button>
|
||||
<button slot="invoker">Dialog</button>
|
||||
<div slot="content" class="dialog">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('dialog-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-dialog>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,77 +1,34 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { runOverlayMixinSuite } from '@lion/overlays/test-suites/OverlayMixin.suite.js';
|
||||
|
||||
import '../lion-dialog.js';
|
||||
|
||||
// Smoke tests dialog
|
||||
describe('lion-dialog', () => {
|
||||
describe('Basic', () => {
|
||||
it('should not be shown by default', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-dialog>
|
||||
<div slot="content" class="dialog">Hey there</div>
|
||||
<lion-button slot="invoker">Popup button</lion-button>
|
||||
</lion-dialog>
|
||||
`);
|
||||
expect(el._overlayCtrl.isShown).to.be.false;
|
||||
describe('Integration tests', () => {
|
||||
const tagString = 'lion-dialog';
|
||||
const tag = unsafeStatic(tagString);
|
||||
|
||||
runOverlayMixinSuite({
|
||||
tagString,
|
||||
tag,
|
||||
suffix: ' for lion-dialog',
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic', () => {
|
||||
it('should show content on invoker click', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-dialog>
|
||||
<div slot="content" class="dialog">
|
||||
Hey there
|
||||
</div>
|
||||
<lion-button slot="invoker">Popup button</lion-button>
|
||||
<button slot="invoker">Popup button</button>
|
||||
</lion-dialog>
|
||||
`);
|
||||
const invoker = el.querySelector('[slot="invoker"]');
|
||||
invoker.click();
|
||||
|
||||
expect(el._overlayCtrl.isShown).to.be.true;
|
||||
});
|
||||
|
||||
it('should hide content on close event', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-dialog>
|
||||
<div slot="content" class="dialog">
|
||||
Hey there
|
||||
<button
|
||||
@click=${e => e.target.dispatchEvent(new Event('dialog-close', { bubbles: true }))}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
<lion-button slot="invoker">Popup button</lion-button>
|
||||
</lion-dialog>
|
||||
`);
|
||||
const invoker = el.querySelector('[slot="invoker"]');
|
||||
invoker.click();
|
||||
|
||||
expect(el._overlayCtrl.isShown).to.be.true;
|
||||
|
||||
const closeBtn = el._overlayCtrl.contentNode.querySelector('button');
|
||||
closeBtn.click();
|
||||
|
||||
expect(el._overlayCtrl.isShown).to.be.false;
|
||||
});
|
||||
|
||||
it('should respond to initially and dynamically setting the config', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-dialog .config=${{ trapsKeyboardFocus: false, viewportConfig: { placement: 'top' } }}>
|
||||
<div slot="content" class="dialog">Hey there</div>
|
||||
<lion-button slot="invoker">Popup button</lion-button>
|
||||
</lion-dialog>
|
||||
`);
|
||||
await el._overlayCtrl.show();
|
||||
expect(el._overlayCtrl.trapsKeyboardFocus).to.be.false;
|
||||
|
||||
el.config = { viewportConfig: { placement: 'left' } };
|
||||
expect(el._overlayCtrl.viewportConfig.placement).to.equal('left');
|
||||
expect(
|
||||
el._overlayCtrl._contentNodeWrapper.classList.contains(
|
||||
'global-overlays__overlay-container--left',
|
||||
),
|
||||
);
|
||||
expect(el.opened).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -90,14 +90,9 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) {
|
|||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__dispatchCloseEvent = this.__dispatchCloseEvent.bind(this);
|
||||
}
|
||||
|
||||
__dispatchCloseEvent() {
|
||||
__dispatchHideEvent() {
|
||||
// Designed to work in conjunction with ModalDialogController
|
||||
this.dispatchEvent(new CustomEvent('dialog-close'), { bubbles: true, composed: true });
|
||||
this.dispatchEvent(new CustomEvent('hide'), { bubbles: true });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -109,7 +104,7 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) {
|
|||
<slot name="heading"></slot>
|
||||
</h1>
|
||||
<button
|
||||
@click="${this.__dispatchCloseEvent}"
|
||||
@click="${this.__dispatchHideEvent}"
|
||||
id="close-button"
|
||||
title="${this.msgLit('lion-calendar-overlay-frame:close')}"
|
||||
aria-label="${this.msgLit('lion-calendar-overlay-frame:close')}"
|
||||
|
|
|
|||
|
|
@ -204,16 +204,23 @@ export class LionInputDatepicker extends OverlayMixin(LionInputDate) {
|
|||
* this is our source to give as .contentNode to OverlayController.
|
||||
* Important: do not change the name of this method.
|
||||
*/
|
||||
// TODO: Refactor to new overlay system public API --> @close=${() => { this.opened = false; }}
|
||||
_overlayTemplate() {
|
||||
// TODO: add performance optimization to only render the calendar if needed
|
||||
return html`
|
||||
<lion-calendar-overlay-frame @dialog-close=${() => this._overlayCtrl.hide()}>
|
||||
<lion-calendar-overlay-frame>
|
||||
<span slot="heading">${this.calendarHeading}</span>
|
||||
${this._calendarTemplate()}
|
||||
</lion-calendar-overlay-frame>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.labelTemplate()} ${this.helpTextTemplate()} ${this.inputGroupTemplate()}
|
||||
${this.feedbackTemplate()} ${this._overlayTemplate()}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclassers can replace this with their custom extension of
|
||||
* LionCalendar, like `<my-calendar id="calendar"></my-calendar>`
|
||||
|
|
@ -331,4 +338,11 @@ export class LionInputDatepicker extends OverlayMixin(LionInputDate) {
|
|||
get _overlayInvokerNode() {
|
||||
return this._invokerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override Configures OverlayMixin
|
||||
*/
|
||||
get _overlayContentNode() {
|
||||
return this.shadowRoot.querySelector('lion-calendar-overlay-frame');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
> Note: Migrating from the old system (`overlays.add(new SomeController({...}))`)? Please check out our [migration guidelines](./docs/migration.md)
|
||||
|
||||
Supports different types of overlays like dialogs, toasts, tooltips, dropdown, etc.
|
||||
|
||||
Manages their position on the screen relative to other elements, including other overlays.
|
||||
|
|
@ -16,7 +14,6 @@ Its purpose is to make it easy to use our Overlay System declaratively. It can b
|
|||
- lion-overlay web component:
|
||||
|
||||
- Show content when clicking the invoker
|
||||
- Respond to overlay-close event in the slot="content" element, to close the content
|
||||
- Have a `.config` object to set or update the OverlayController's configuration
|
||||
|
||||
- [**OverlaysManager**](./docs/OverlaysManager.md), a global repository keeping track of all different types of overlays
|
||||
|
|
@ -37,14 +34,14 @@ npm i --save @lion/overlays
|
|||
import '@lion/overlays/lion-overlay.js';
|
||||
|
||||
html`
|
||||
<lion-overlay.config=${{
|
||||
<lion-overlay .config=${{
|
||||
placementMode: 'global',
|
||||
viewportConfig: { placement: 'bottom-right' },
|
||||
}}>
|
||||
<div slot="content">
|
||||
This is an overlay
|
||||
<button
|
||||
@click=${e => e.target.dispatchEvent(new Event('overlay-close', { bubbles: true }))}
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>x</button>
|
||||
<div>
|
||||
<button slot="invoker">
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ All boolean flags default to 'false'.
|
|||
```text
|
||||
- {Boolean} trapsKeyboardFocus - rotates tab.
|
||||
- {Boolean} hidesOnEsc - hides the overlay when pressing [esc].
|
||||
- {Boolean} hidesOnHideEventInContentNode - (defaults to true) hides if an event called "hide" is fired within the content
|
||||
- {Boolean} hidesOnOutsideClick - hides if user clicks outside of the overlay
|
||||
- {Element} elementToFocusAfterHide - the element that should be called `.focus()` on after dialog closes.
|
||||
- {Boolean} hasBackdrop - whether it should have a backdrop. (local mode only)
|
||||
- {Boolean} isBlocking - hides other overlays when multiple are opened.
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
# Migration Guidelines Overlay System
|
||||
|
||||
If you are still using the old overlay system, we encourage you to migrate. The new way is more reliable, less error-prone and a lot easier to maintain. In addition, we now have a web component `lion-dialog` which is a declarative way of adding a modal dialog inside your template!
|
||||
|
||||
## Declaratively (encouraged)
|
||||
|
||||
Using generic `lion-overlay`:
|
||||
|
||||
```js
|
||||
import { withBottomSheetConfig } from '@lion/overlays';
|
||||
import '@lion/overlays/lion-overlay.js';
|
||||
|
||||
const template = html`
|
||||
<lion-overlay
|
||||
.config=${{
|
||||
...withBottomSheetConfig(),
|
||||
viewportConfig: { placement: 'top-right' },
|
||||
}}
|
||||
>
|
||||
<button slot="invoker">Click me!</button>
|
||||
<div slot="content">
|
||||
<div>Hello, World!</div>
|
||||
<button @click=${e => e.target.dispatchEvent(new Event('overlay-close', { bubbles: true }))}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</lion-overlay>
|
||||
`;
|
||||
```
|
||||
|
||||
Or using a more specific component like `lion-tooltip`, which toggles on-hover:
|
||||
|
||||
```js
|
||||
import '@lion/tooltip/lion-tooltip.js';
|
||||
|
||||
const template = html`
|
||||
<lion-tooltip .config=${{ popperConfig: { placement: 'top-right' } }}>
|
||||
<button slot="invoker">Hover me!</button>
|
||||
<div slot="content">
|
||||
<div>Hello, World!</div>
|
||||
</div>
|
||||
</lion-tooltip>
|
||||
`;
|
||||
```
|
||||
|
||||
Or `lion-dialog` which uses modal dialog configuration defaults
|
||||
|
||||
```js
|
||||
import '@lion/dialog/lion-dialog.js';
|
||||
|
||||
const template = html`
|
||||
<lion-dialog .config=${{ viewportConfig: { placement: 'top-right' } }}>
|
||||
<button slot="invoker">Click me!</button>
|
||||
<div slot="content">
|
||||
<div>Hello, World!</div>
|
||||
<button @click=${e => e.target.dispatchEvent(new Event('dialog-close', { bubbles: true }))}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</lion-dialog>
|
||||
`;
|
||||
```
|
||||
|
||||
## Instantiating an overlay controller (discouraged)
|
||||
|
||||
### Old
|
||||
|
||||
```js
|
||||
import { overlays, GlobalOverlayController } from '@lion/overlays';
|
||||
|
||||
const ctrl = overlays.add(
|
||||
new GlobalOverlayController({
|
||||
contentTemplate: () => html`
|
||||
<div>My content</div>
|
||||
`,
|
||||
}),
|
||||
);
|
||||
|
||||
const template = html`
|
||||
<lion-button @click="${event => ctrl.show(event.target)}">
|
||||
Open dialog
|
||||
</lion-button>
|
||||
`;
|
||||
```
|
||||
|
||||
### New
|
||||
|
||||
> Note: The OverlayController is render-system agnostic, you are responsible for passing a node (and rendering it prior).
|
||||
> For lit-html, we will use a simple helper. Let us know if you think we should export this.
|
||||
|
||||
```js
|
||||
import { render } from '@lion/core';
|
||||
|
||||
function renderOffline(litHtmlTemplate) {
|
||||
const offlineRenderContainer = document.createElement('div');
|
||||
render(litHtmlTemplate, offlineRenderContainer);
|
||||
return offlineRenderContainer.firstElementChild;
|
||||
}
|
||||
```
|
||||
|
||||
This example shows how you can use our configuration generators.
|
||||
|
||||
```js
|
||||
import { OverlayController, withModalDialogConfig } from '@lion/overlays';
|
||||
|
||||
const ctrl = new OverlayController({
|
||||
...withModalDialogConfig(),
|
||||
contentTemplate: renderOffline(html`
|
||||
<div>My content</div>
|
||||
`),
|
||||
});
|
||||
|
||||
const template = html`
|
||||
<lion-button @click="${event => ctrl.show(event.target)}">
|
||||
Open dialog
|
||||
</lion-button>
|
||||
`;
|
||||
```
|
||||
|
||||
### New (local example)
|
||||
|
||||
```js
|
||||
import { OverlayController } from '@lion/overlays';
|
||||
|
||||
const ctrl = new OverlayController({
|
||||
...withModalDialogConfig(),
|
||||
placementMode: 'local',
|
||||
hidesOnEsc: true,
|
||||
hidesOnOutsideClick: true,
|
||||
contentNode: renderOffline(html`
|
||||
<div>United Kingdom</div>
|
||||
`),
|
||||
invokerNode: renderOffline(html`
|
||||
<button @click=${() => ctrl.toggle()}>UK</button>
|
||||
`),
|
||||
});
|
||||
|
||||
const template = html`
|
||||
<div>In the ${ctrl.invoker}${ctrl.content} the weather is nice.</div>
|
||||
`;
|
||||
```
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
"stories",
|
||||
"test",
|
||||
"test-helpers",
|
||||
"test-suites",
|
||||
"translations",
|
||||
"*.js"
|
||||
],
|
||||
|
|
@ -37,8 +38,6 @@
|
|||
"popper.js": "^1.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lion/button": "^0.3.43",
|
||||
"@lion/icon": "^0.2.9",
|
||||
"@open-wc/demoing-storybook": "^0.2.0",
|
||||
"@open-wc/testing": "^2.3.4",
|
||||
"@open-wc/testing-helpers": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export class OverlayController {
|
|||
trapsKeyboardFocus: false,
|
||||
hidesOnEsc: false,
|
||||
hidesOnOutsideClick: false,
|
||||
hidesOnHideEventInContentNode: true,
|
||||
isTooltip: false,
|
||||
handlesUserInteraction: false,
|
||||
handlesAccessibility: false,
|
||||
|
|
@ -336,6 +337,9 @@ export class OverlayController {
|
|||
if (this.hidesOnOutsideClick) {
|
||||
this._handleHidesOnOutsideClick({ phase });
|
||||
}
|
||||
if (this.hidesOnHideEventInContentNode) {
|
||||
this._handleHidesOnHideEventInContentNode({ phase });
|
||||
}
|
||||
if (this.handlesAccessibility) {
|
||||
this._handleAccessibility({ phase });
|
||||
}
|
||||
|
|
@ -483,12 +487,28 @@ export class OverlayController {
|
|||
if (phase === 'show') {
|
||||
this.__escKeyHandler = ev => ev.key === 'Escape' && this.hide();
|
||||
this.contentNode.addEventListener('keyup', this.__escKeyHandler);
|
||||
if (this.invokerNode) {
|
||||
this.invokerNode.addEventListener('keyup', this.__escKeyHandler);
|
||||
}
|
||||
} else if (phase === 'hide') {
|
||||
this.contentNode.removeEventListener('keyup', this.__escKeyHandler);
|
||||
if (this.invokerNode) {
|
||||
this.invokerNode.removeEventListener('keyup', this.__escKeyHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleHidesOnHideEventInContentNode({ phase }) {
|
||||
if (phase === 'show') {
|
||||
this.__hideEventInContentNodeHandler = ev => {
|
||||
ev.stopPropagation();
|
||||
this.hide();
|
||||
};
|
||||
this.contentNode.addEventListener('hide', this.__hideEventInContentNodeHandler);
|
||||
} else if (phase === 'hide') {
|
||||
this.contentNode.removeEventListener('keyup', this.__hideEventInContentNodeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
_handleInheritsReferenceWidth() {
|
||||
if (!this._referenceNode) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { render, dedupeMixin } from '@lion/core';
|
||||
import { dedupeMixin } from '@lion/core';
|
||||
import { OverlayController } from './OverlayController.js';
|
||||
|
||||
/**
|
||||
|
|
@ -16,54 +16,26 @@ export const OverlayMixin = dedupeMixin(
|
|||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
},
|
||||
closeEventName: {
|
||||
type: String,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.opened = false;
|
||||
this.config = {};
|
||||
this.closeEventName = 'overlay-close';
|
||||
}
|
||||
|
||||
get opened() {
|
||||
return this._overlayCtrl.isShown;
|
||||
}
|
||||
|
||||
set opened(show) {
|
||||
if (show) {
|
||||
this.dispatchEvent(new Event('before-show'));
|
||||
}
|
||||
this._opened = show; // mainly captured for sync on connectedCallback
|
||||
if (this._overlayCtrl) {
|
||||
this.__syncOpened();
|
||||
}
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this._config;
|
||||
return this.__config;
|
||||
}
|
||||
|
||||
set config(value) {
|
||||
if (this._overlayCtrl) {
|
||||
this._overlayCtrl.updateConfig(value);
|
||||
}
|
||||
this._config = value;
|
||||
this.__config = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @overridable method `_overlayTemplate`
|
||||
* Be aware that the overlay will be placed in a different shadow root.
|
||||
* Therefore, style encapsulation should be provided by the contents of
|
||||
* _overlayTemplate
|
||||
* @return {TemplateResult}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @overridable method `_defineOverlay`
|
||||
* @desc returns an instance of a (dynamic) overlay controller
|
||||
|
|
@ -88,7 +60,19 @@ export const OverlayMixin = dedupeMixin(
|
|||
*/
|
||||
// eslint-disable-next-line
|
||||
_defineOverlayConfig() {
|
||||
return {};
|
||||
return {
|
||||
placementMode: 'local',
|
||||
};
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('opened')) {
|
||||
if (this._overlayCtrl) {
|
||||
this.__syncToOverlayController();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,44 +90,19 @@ export const OverlayMixin = dedupeMixin(
|
|||
// eslint-disable-next-line class-methods-use-this
|
||||
_teardownOpenCloseListeners() {}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this._createOverlay();
|
||||
|
||||
// Default close event catcher on the contentNode which is useful if people want to close
|
||||
// their overlay but the content is not in the global root node (nowhere near the overlay component)
|
||||
this.__close = () => {
|
||||
this.opened = false;
|
||||
};
|
||||
this._overlayCtrl.contentNode.addEventListener(this.closeEventName, this.__close);
|
||||
|
||||
this._setupOpenCloseListeners();
|
||||
this.__syncOpened();
|
||||
this.__syncPopper();
|
||||
}
|
||||
|
||||
firstUpdated(c) {
|
||||
super.firstUpdated(c);
|
||||
this._createOutletForLocalOverlay();
|
||||
}
|
||||
|
||||
updated(c) {
|
||||
super.updated(c);
|
||||
if (this.__managesOverlayViaTemplate) {
|
||||
this._renderOverlayContent();
|
||||
}
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
// we setup in firstUpdated so we can use nodes from light and shadowDom
|
||||
this._setupOverlayCtrl();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (super.disconnectedCallback) {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
this.opened = false;
|
||||
this._overlayCtrl.contentNode.removeEventListener(this.closeEventName, this.__close);
|
||||
this._teardownOpenCloseListeners();
|
||||
this._overlayCtrl.teardown();
|
||||
if (this._overlayCtrl) {
|
||||
this._teardownOverlayCtrl();
|
||||
}
|
||||
}
|
||||
|
||||
get _overlayInvokerNode() {
|
||||
|
|
@ -171,69 +130,51 @@ export const OverlayMixin = dedupeMixin(
|
|||
return contentNode || this._cachedOverlayContentNode;
|
||||
}
|
||||
|
||||
_renderOverlayContent() {
|
||||
render(this._overlayTemplate(), this.__contentParent, {
|
||||
scopeName: this.localName,
|
||||
eventContext: this,
|
||||
_setupOverlayCtrl() {
|
||||
this._overlayCtrl = this._defineOverlay({
|
||||
contentNode: this._overlayContentNode,
|
||||
invokerNode: this._overlayInvokerNode,
|
||||
});
|
||||
this.__syncToOverlayController();
|
||||
this.__setupSyncFromOverlayController();
|
||||
|
||||
this._setupOpenCloseListeners();
|
||||
}
|
||||
|
||||
_createOverlay() {
|
||||
let contentNode;
|
||||
if (this.__managesOverlayViaTemplate) {
|
||||
this.__contentParent = document.createElement('div');
|
||||
this._renderOverlayContent();
|
||||
contentNode = this.__contentParent.firstElementChild;
|
||||
} else {
|
||||
contentNode = this._overlayContentNode;
|
||||
_teardownOverlayCtrl() {
|
||||
this._teardownOpenCloseListeners();
|
||||
this.__teardownSyncFromOverlayController();
|
||||
this._overlayCtrl.teardown();
|
||||
}
|
||||
|
||||
// Why no template support for invokerNode?
|
||||
// -> Because this node will always be managed by the Subclasser and should
|
||||
// reside in the dom of the sub class. A reference to a rendered node suffices.
|
||||
const invokerNode = this._overlayInvokerNode;
|
||||
this._overlayCtrl = this._defineOverlay({ contentNode, invokerNode });
|
||||
__setupSyncFromOverlayController() {
|
||||
this.__onOverlayCtrlShow = () => {
|
||||
this.opened = true;
|
||||
};
|
||||
this.__onOverlayCtrlHide = () => {
|
||||
this.opened = false;
|
||||
};
|
||||
this.__onBeforeShow = () => {
|
||||
this.dispatchEvent(new Event('before-show'));
|
||||
};
|
||||
|
||||
this._overlayCtrl.addEventListener('show', this.__onOverlayCtrlShow);
|
||||
this._overlayCtrl.addEventListener('hide', this.__onOverlayCtrlHide);
|
||||
this._overlayCtrl.addEventListener('before-show', this.__onBeforeShow);
|
||||
}
|
||||
|
||||
// FIXME: We add an overlay slot to the wrapper, but the content node already has a slot="content"
|
||||
// This is a big problem, because slots should be direct children of its host element.
|
||||
// Putting the shadow outlet slot in between breaks that. https://github.com/ing-bank/lion/issues/382
|
||||
/**
|
||||
* @desc Should be called by Subclasser for local overlay support in shadow roots
|
||||
* Create an outlet slot in shadow dom that our local overlay can pass through
|
||||
*/
|
||||
_createOutletForLocalOverlay() {
|
||||
const outlet = document.createElement('slot');
|
||||
outlet.name = '_overlay-shadow-outlet';
|
||||
this.shadowRoot.appendChild(outlet);
|
||||
this._overlayCtrl._contentNodeWrapper.slot = '_overlay-shadow-outlet';
|
||||
__teardownSyncFromOverlayController() {
|
||||
this._overlayCtrl.removeEventListener('show', this.__onOverlayCtrlShow);
|
||||
this._overlayCtrl.removeEventListener('hide', this.__onOverlayCtrlHide);
|
||||
this._overlayCtrl.removeEventListener('before-show', this.__onBeforeShow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Two options for a Subclasser:
|
||||
* - 1: Define a template in `._overlayTemplate`. In this case the overlay content is
|
||||
* predefined and thus belongs to the web component. Examples: datepicker.
|
||||
* - 2: Define a getter `_overlayContentNode` that returns a node reference to a (content
|
||||
* projected) node. Used when Application Developer is in charge of the content. Examples:
|
||||
* popover, dialog, bottom sheet, dropdown, tooltip, select, combobox etc.
|
||||
*/
|
||||
get __managesOverlayViaTemplate() {
|
||||
return Boolean(this._overlayTemplate);
|
||||
}
|
||||
|
||||
__syncOpened() {
|
||||
if (this._opened) {
|
||||
__syncToOverlayController() {
|
||||
if (this.opened) {
|
||||
this._overlayCtrl.show();
|
||||
} else {
|
||||
this._overlayCtrl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
__syncPopper() {
|
||||
if (this._overlayCtrl) {
|
||||
// TODO: Use updateConfig directly.. But maybe we can remove this entirely.
|
||||
this._overlayCtrl.updatePopperConfig(this.config.popperConfig);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { storiesOf, html, withKnobs } from '@open-wc/demoing-storybook';
|
||||
import { css, render, LitElement } from '@lion/core';
|
||||
import '@lion/icon/lion-icon.js';
|
||||
import '@lion/button/lion-button.js';
|
||||
import {
|
||||
withBottomSheetConfig,
|
||||
withDropdownConfig,
|
||||
|
|
@ -70,7 +68,7 @@ const overlayDemoStyle = css`
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
.demo-overlay {
|
||||
display: block;
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
|
|
@ -80,7 +78,7 @@ const overlayDemoStyle = css`
|
|||
padding: 8px;
|
||||
}
|
||||
|
||||
.overlay lion-button {
|
||||
.demo-overlay button {
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
|
@ -93,11 +91,6 @@ const overlayDemoStyle = css`
|
|||
customElements.define(
|
||||
'lion-demo-overlay',
|
||||
class extends OverlayMixin(LitElement) {
|
||||
constructor() {
|
||||
super();
|
||||
this.closeEventName = 'demo-overlay-close';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_defineOverlayConfig() {
|
||||
return {
|
||||
|
|
@ -107,24 +100,20 @@ customElements.define(
|
|||
|
||||
_setupOpenCloseListeners() {
|
||||
this.__toggle = () => {
|
||||
console.log('toggle!');
|
||||
this.opened = !this.opened;
|
||||
};
|
||||
|
||||
console.log(this._overlayCtrl.invokerNode, this, this.__toggle);
|
||||
this._overlayCtrl.invokerNode.addEventListener('click', this.__toggle);
|
||||
this._overlayCtrl.invokerNode.addEventListener('click', () => console.log('ay'));
|
||||
this._overlayInvokerNode.addEventListener('click', this.__toggle);
|
||||
}
|
||||
|
||||
_teardownOpenCloseListeners() {
|
||||
console.log('teardown for', this);
|
||||
this._overlayCtrl.invokerNode.removeEventListener('click', this.__toggle);
|
||||
this._overlayInvokerNode.removeEventListener('click', this.__toggle);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="content"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
`;
|
||||
}
|
||||
},
|
||||
|
|
@ -145,25 +134,25 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
</p>
|
||||
<p>
|
||||
To close your overlay from some action performed inside the content slot, fire a
|
||||
<code>close</code> event.
|
||||
<code>hide</code> event.
|
||||
</p>
|
||||
<p>
|
||||
For the overlay to close, it will need to bubble to the content slot (use
|
||||
<code>bubbles: true</code>. Also <code>composed: true</code> if it needs to traverse shadow
|
||||
boundaries)
|
||||
<code>bubbles: true</code>. If absolutely needed <code>composed: true</code> can be used to
|
||||
traverse shadow boundaries)
|
||||
</p>
|
||||
<p>The demo below demonstrates this</p>
|
||||
<div class="demo-box">
|
||||
<lion-demo-overlay>
|
||||
<lion-button slot="invoker">Overlay</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
</div>
|
||||
|
|
@ -174,15 +163,15 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
<lion-demo-overlay
|
||||
.config=${{ hasBackdrop: true, trapsKeyboardFocus: true, viewportConfig: { placement } }}
|
||||
>
|
||||
<lion-button slot="invoker">Overlay ${placement}</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay ${placement}</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
`;
|
||||
|
|
@ -204,27 +193,25 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
${overlayDemoStyle}
|
||||
</style>
|
||||
<lion-demo-overlay .config=${{ ...withModalDialogConfig() }}>
|
||||
<lion-button slot="invoker">Overlay</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
<div>
|
||||
Hello! This is a notification.
|
||||
<lion-button
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>Close</lion-button
|
||||
>
|
||||
<button @click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}>
|
||||
Close
|
||||
</button>
|
||||
<lion-demo-overlay
|
||||
.config=${{ ...withModalDialogConfig(), viewportConfig: { placement: 'top' } }}
|
||||
>
|
||||
<lion-button slot="invoker">Open child</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Open child</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
</div>
|
||||
|
|
@ -242,15 +229,15 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
<lion-demo-overlay
|
||||
.config=${{ placementMode: 'local', popperConfig: { placement: 'bottom-start' } }}
|
||||
>
|
||||
<lion-button slot="invoker">Overlay</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
</div>
|
||||
|
|
@ -307,15 +294,15 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
.add('Switch overlays configuration', () => {
|
||||
const overlay = renderOffline(html`
|
||||
<lion-demo-overlay .config=${{ ...withBottomSheetConfig() }}>
|
||||
<lion-button slot="invoker">Overlay</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
`);
|
||||
|
|
@ -376,15 +363,15 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
}
|
||||
}}
|
||||
>
|
||||
<lion-button slot="invoker">Overlay</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e =>
|
||||
e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
`,
|
||||
|
|
@ -411,7 +398,7 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
}}
|
||||
>UK</span
|
||||
>
|
||||
<div slot="content" class="overlay">
|
||||
<div slot="content" class="demo-overlay">
|
||||
United Kingdom
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
|
|
@ -475,13 +462,13 @@ storiesOf('Overlay System | Overlay as a WC', module)
|
|||
: { popperConfig: { placement: text('local config', 'top-start') } }),
|
||||
}}
|
||||
>
|
||||
<lion-button slot="invoker">Overlay</lion-button>
|
||||
<div slot="content" class="overlay">
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<lion-button
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${e => e.target.dispatchEvent(new Event('demo-overlay-close', { bubbles: true }))}
|
||||
>⨯</lion-button
|
||||
@click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}
|
||||
>⨯</button
|
||||
>
|
||||
</div>
|
||||
</lion-demo-overlay>
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ storiesOf('Overlay System | Behavior Features', module)
|
|||
${this.options[(this.options.indexOf(this.placement) + 1) % this.options.length]}
|
||||
position
|
||||
</button>
|
||||
<button @click="${() => this.dispatchEvent(new CustomEvent('close'))}">Close</button>
|
||||
<button @click="${() => this.dispatchEvent(new Event('hide'))}">Close</button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
@ -265,9 +265,6 @@ storiesOf('Overlay System | Behavior Features', module)
|
|||
element.addEventListener('toggle-placement', e => {
|
||||
overlayCtrl.updateConfig({ viewportConfig: { placement: e.detail } });
|
||||
});
|
||||
element.addEventListener('close', () => {
|
||||
overlayCtrl.hide();
|
||||
});
|
||||
return html`
|
||||
<style>
|
||||
${globalOverlayDemoStyle}
|
||||
|
|
|
|||
57
packages/overlays/test-suites/OverlayMixin.suite.js
Normal file
57
packages/overlays/test-suites/OverlayMixin.suite.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { expect, fixture, html, aTimeout } from '@open-wc/testing';
|
||||
|
||||
export function runOverlayMixinSuite({ /* tagString, */ tag, suffix = '' }) {
|
||||
describe(`OverlayMixin${suffix}`, () => {
|
||||
let el;
|
||||
|
||||
beforeEach(async () => {
|
||||
el = await fixture(html`
|
||||
<${tag}>
|
||||
<div slot="content">content of the overlay</div>
|
||||
<button slot="invoker">invoker button</button>
|
||||
</${tag}>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not be opened by default', async () => {
|
||||
expect(el.opened).to.be.false;
|
||||
expect(el._overlayCtrl.isShown).to.be.false;
|
||||
});
|
||||
|
||||
it('syncs opened to overlayController', async () => {
|
||||
el.opened = true;
|
||||
expect(el.opened).to.be.true;
|
||||
await aTimeout(); // overlayCtrl show/hide is async
|
||||
expect(el._overlayCtrl.isShown).to.be.true;
|
||||
|
||||
el.opened = false;
|
||||
expect(el.opened).to.be.false;
|
||||
await aTimeout(0); // overlayCtrl show/hide is async
|
||||
expect(el._overlayCtrl.isShown).to.be.false;
|
||||
});
|
||||
|
||||
it('syncs overlayController to opened', async () => {
|
||||
expect(el.opened).to.be.false;
|
||||
await el._overlayCtrl.show();
|
||||
expect(el.opened).to.be.true;
|
||||
|
||||
await el._overlayCtrl.hide();
|
||||
expect(el.opened).to.be.false;
|
||||
});
|
||||
|
||||
it('should respond to initially and dynamically setting the config', async () => {
|
||||
const itEl = await fixture(html`
|
||||
<${tag} .config=${{ trapsKeyboardFocus: false, viewportConfig: { placement: 'top' } }}>
|
||||
<div slot="content">content of the overlay</div>
|
||||
<button slot="invoker">invoker button</button>
|
||||
</${tag}>
|
||||
`);
|
||||
itEl.opened = true;
|
||||
await itEl.updateComplete;
|
||||
expect(itEl._overlayCtrl.trapsKeyboardFocus).to.be.false;
|
||||
|
||||
itEl.config = { viewportConfig: { placement: 'left' } };
|
||||
expect(itEl._overlayCtrl.viewportConfig.placement).to.equal('left');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -314,6 +314,33 @@ describe('OverlayController', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('hidesOnHideEventInContentNode', () => {
|
||||
it('hides content on hide event within the content ', async () => {
|
||||
const ctrl = new OverlayController({
|
||||
...withGlobalTestConfig(),
|
||||
hidesOnHideEventInContentNode: true,
|
||||
contentNode: fixtureSync(html`
|
||||
<div>
|
||||
my content
|
||||
<button @click=${e => e.target.dispatchEvent(new Event('hide', { bubbles: true }))}>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
`),
|
||||
});
|
||||
await ctrl.show();
|
||||
|
||||
const closeBtn = ctrl.contentNode.querySelector('button');
|
||||
closeBtn.click();
|
||||
|
||||
expect(ctrl.isShown).to.be.false;
|
||||
});
|
||||
|
||||
it('does stop propagation of the "hide" event to not pollute the event stack and to prevent side effects', () => {
|
||||
// TODO: how to test this?
|
||||
});
|
||||
});
|
||||
|
||||
describe('hidesOnOutsideClick', () => {
|
||||
it('hides on outside click', async () => {
|
||||
const contentNode = await fixture('<div>Content</div>');
|
||||
|
|
|
|||
12
packages/overlays/test/OverlayMixin.test.js
Normal file
12
packages/overlays/test/OverlayMixin.test.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { defineCE, unsafeStatic } from '@open-wc/testing';
|
||||
import { LitElement } from '@lion/core';
|
||||
import { runOverlayMixinSuite } from '../test-suites/OverlayMixin.suite.js';
|
||||
import { OverlayMixin } from '../src/OverlayMixin.js';
|
||||
|
||||
const tagString = defineCE(class extends OverlayMixin(LitElement) {});
|
||||
const tag = unsafeStatic(tagString);
|
||||
|
||||
runOverlayMixinSuite({
|
||||
tagString,
|
||||
tag,
|
||||
});
|
||||
|
|
@ -182,12 +182,6 @@ export class LionSelectRich extends OverlayMixin(
|
|||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
this.__setupOverlay();
|
||||
this.__setupInvokerNode();
|
||||
this.__setupListboxNode();
|
||||
|
||||
this._invokerNode.selectedElement = this.formElements[this.checkedIndex];
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
|
@ -202,6 +196,12 @@ export class LionSelectRich extends OverlayMixin(
|
|||
|
||||
firstUpdated(c) {
|
||||
super.firstUpdated(c);
|
||||
this.__setupOverlay();
|
||||
this.__setupInvokerNode();
|
||||
this.__setupListboxNode();
|
||||
|
||||
this._invokerNode.selectedElement = this.formElements[this.checkedIndex];
|
||||
|
||||
this.__toggleInvokerDisabled();
|
||||
}
|
||||
|
||||
|
|
@ -243,6 +243,14 @@ export class LionSelectRich extends OverlayMixin(
|
|||
return this.querySelector('[slot="input"]');
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.labelTemplate()} ${this.helpTextTemplate()} ${this.inputGroupTemplate()}
|
||||
${this.feedbackTemplate()}
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ describe('lion-select-rich', () => {
|
|||
</lion-select-rich>
|
||||
`);
|
||||
el._invokerNode.click();
|
||||
await el.updateComplete;
|
||||
await aTimeout();
|
||||
expect(el.opened).to.be.true;
|
||||
});
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ describe('lion-select-rich', () => {
|
|||
</lion-select-rich>
|
||||
`);
|
||||
el._invokerNode.click();
|
||||
await el.updateComplete;
|
||||
await aTimeout();
|
||||
expect(el.opened).to.be.true;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,6 @@
|
|||
"@lion/overlays": "^0.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lion/button": "^0.3.43",
|
||||
"@lion/icon": "^0.2.9",
|
||||
"@open-wc/demoing-storybook": "^0.2.0",
|
||||
"@open-wc/testing": "^2.3.4"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export class LionTooltip extends OverlayMixin(LitElement) {
|
|||
return html`
|
||||
<slot name="invoker"></slot>
|
||||
<slot name="content"></slot>
|
||||
<slot name="_overlay-shadow-outlet"></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { storiesOf, html, withKnobs, object, text } from '@open-wc/demoing-storybook';
|
||||
import { css } from '@lion/core';
|
||||
|
||||
import '@lion/icon/lion-icon.js';
|
||||
import '@lion/button/lion-button.js';
|
||||
import '../lion-tooltip.js';
|
||||
|
||||
const tooltipDemoStyle = css`
|
||||
|
|
@ -53,7 +51,7 @@ storiesOf('Overlays Specific WC|Tooltip', module)
|
|||
</style>
|
||||
<div class="demo-box">
|
||||
<lion-tooltip .config=${{ popperConfig: { placement: 'right' } }}>
|
||||
<lion-button slot="invoker">Tooltip</lion-button>
|
||||
<button slot="invoker">Tooltip</button>
|
||||
<div slot="content" class="demo-tooltip">Hello there!</div>
|
||||
</lion-tooltip>
|
||||
</div>
|
||||
|
|
@ -67,19 +65,19 @@ storiesOf('Overlays Specific WC|Tooltip', module)
|
|||
</style>
|
||||
<div class="demo-box_placements">
|
||||
<lion-tooltip .config=${{ popperConfig: { placement: 'top' } }}>
|
||||
<lion-button slot="invoker">Top</lion-button>
|
||||
<button slot="invoker">Top</button>
|
||||
<div slot="content" class="demo-tooltip">Its top placement</div>
|
||||
</lion-tooltip>
|
||||
<lion-tooltip .config=${{ popperConfig: { placement: 'right' } }}>
|
||||
<lion-button slot="invoker">Right</lion-button>
|
||||
<button slot="invoker">Right</button>
|
||||
<div slot="content" class="demo-tooltip">Its right placement</div>
|
||||
</lion-tooltip>
|
||||
<lion-tooltip .config=${{ popperConfig: { placement: 'bottom' } }}>
|
||||
<lion-button slot="invoker">Bottom</lion-button>
|
||||
<button slot="invoker">Bottom</button>
|
||||
<div slot="content" class="demo-tooltip">Its bottom placement</div>
|
||||
</lion-tooltip>
|
||||
<lion-tooltip .config=${{ popperConfig: { placement: 'left' } }}>
|
||||
<lion-button slot="invoker">Left</lion-button>
|
||||
<button slot="invoker">Left</button>
|
||||
<div slot="content" class="demo-tooltip">Its left placement</div>
|
||||
</lion-tooltip>
|
||||
</div>
|
||||
|
|
@ -119,7 +117,7 @@ storiesOf('Overlays Specific WC|Tooltip', module)
|
|||
}),
|
||||
}}"
|
||||
>
|
||||
<lion-button slot="invoker">${text('Invoker text', 'Hover me!')}</lion-button>
|
||||
<button slot="invoker">${text('Invoker text', 'Hover me!')}</button>
|
||||
<div slot="content" class="demo-tooltip">${text('Content text', 'Hello, World!')}</div>
|
||||
</lion-tooltip>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { runOverlayMixinSuite } from '@lion/overlays/test-suites/OverlayMixin.suite.js';
|
||||
|
||||
import '../lion-tooltip.js';
|
||||
|
||||
describe('lion-tooltip', () => {
|
||||
describe('Basic', () => {
|
||||
it('should not be shown by default', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-tooltip>
|
||||
<div slot="content">Hey there</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
expect(el._overlayCtrl.isShown).to.equal(false);
|
||||
describe('Integration tests', () => {
|
||||
const tagString = 'lion-tooltip';
|
||||
const tag = unsafeStatic(tagString);
|
||||
|
||||
runOverlayMixinSuite({
|
||||
tagString,
|
||||
tag,
|
||||
suffix: ' for lion-tooltip',
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic', () => {
|
||||
it('should show content on mouseenter and hide on mouseleave', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-tooltip>
|
||||
<div slot="content">Hey there</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
const eventMouseEnter = new Event('mouseenter');
|
||||
|
|
@ -35,7 +37,7 @@ describe('lion-tooltip', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-tooltip>
|
||||
<div slot="content">Hey there</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
const eventMouseEnter = new Event('mouseenter');
|
||||
|
|
@ -52,7 +54,7 @@ describe('lion-tooltip', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-tooltip>
|
||||
<div slot="content">Hey there</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
const invoker = Array.from(el.children).find(child => child.slot === 'invoker');
|
||||
|
|
@ -70,7 +72,7 @@ describe('lion-tooltip', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-tooltip>
|
||||
<div slot="content">Hey there</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
const invoker = Array.from(el.children).find(child => child.slot === 'invoker');
|
||||
|
|
@ -90,7 +92,7 @@ describe('lion-tooltip', () => {
|
|||
<div slot="content">
|
||||
This is Tooltip using <strong id="click_overlay">overlay</strong>
|
||||
</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
const invoker = Array.from(el.children).find(child => child.slot === 'invoker');
|
||||
|
|
@ -106,7 +108,7 @@ describe('lion-tooltip', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-tooltip>
|
||||
<div slot="content">Hey there</div>
|
||||
<lion-button slot="invoker">Tooltip button</lion-button>
|
||||
<button slot="invoker">Tooltip button</button>
|
||||
</lion-tooltip>
|
||||
`);
|
||||
|
||||
|
|
|
|||
|
|
@ -2088,7 +2088,7 @@
|
|||
wallaby-webpack "^3.0.0"
|
||||
webpack "^4.28.0"
|
||||
|
||||
"@open-wc/testing@^2.3.4", "@open-wc/testing@^2.3.9":
|
||||
"@open-wc/testing@^2.3.4":
|
||||
version "2.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-2.3.9.tgz#048bb3122d989cf0df96611513aaec7738964e3d"
|
||||
integrity sha512-5pKtHNP/73y9VWAwXOdxf4uzKVAtCowSdy4B6It4iETq8RshkAtKJbJBj+iQSU81pG6jOgSNPlGYeU01/CXaxw==
|
||||
|
|
|
|||
Loading…
Reference in a new issue