feat(popup): change API to popper based

This commit is contained in:
Joren Broekema 2019-06-27 14:10:12 +02:00
parent 22357ea81f
commit 1e6d60dfdb
3 changed files with 96 additions and 25 deletions

View file

@ -4,29 +4,44 @@ import { overlays, LocalOverlayController } from '@lion/overlays';
export class LionPopup extends UpdatingElement { export class LionPopup extends UpdatingElement {
static get properties() { static get properties() {
return { return {
position: { placementConfig: {
type: String, type: Object,
}, },
}; };
} }
get placementConfig() {
return this._placementConfig;
}
set placementConfig(config) {
this._placementConfig = {
...this._placementConfig,
...config,
};
if (this._controller && this._controller._popper) {
this._controller.updatePlacementConfig(this._placementConfig);
}
}
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this.contenNode = this.querySelector('[slot="content"]'); this.contentNode = this.querySelector('[slot="content"]');
this.invokerNode = this.querySelector('[slot="invoker"]'); this.invokerNode = this.querySelector('[slot="invoker"]');
this._popup = overlays.add( this._controller = overlays.add(
new LocalOverlayController({ new LocalOverlayController({
hidesOnEsc: true, hidesOnEsc: true,
hidesOnOutsideClick: true, hidesOnOutsideClick: true,
placement: this.position, placementConfig: this.placementConfig,
contentNode: this.contenNode, contentNode: this.contentNode,
invokerNode: this.invokerNode, invokerNode: this.invokerNode,
}), }),
); );
this._show = () => this._popup.show(); this._show = () => this._controller.show();
this._hide = () => this._popup.hide(); this._hide = () => this._controller.hide();
this._toggle = () => this._popup.toggle(); this._toggle = () => this._controller.toggle();
this.invokerNode.addEventListener('click', this._toggle); this.invokerNode.addEventListener('click', this._toggle);
} }

View file

@ -1,4 +1,4 @@
import { storiesOf, html } from '@open-wc/demoing-storybook'; import { storiesOf, html, withKnobs, object, text } from '@open-wc/demoing-storybook';
import { css } from '@lion/core'; import { css } from '@lion/core';
import '@lion/icon/lion-icon.js'; import '@lion/icon/lion-icon.js';
@ -11,11 +11,11 @@ const popupDemoStyle = css`
background-color: white; background-color: white;
border-radius: 2px; border-radius: 2px;
border: 1px solid grey; border: 1px solid grey;
margin: 250px; margin: 250px 0 0 250px;
padding: 8px; padding: 8px;
} }
.demo-box_positions { .demo-box_placements {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 173px; width: 173px;
@ -40,6 +40,8 @@ const popupDemoStyle = css`
background-color: black; background-color: black;
border-radius: 4px; border-radius: 4px;
padding: 8px; padding: 8px;
/* To display on top of elements with no z-index that are appear later in the DOM */
z-index: 1;
} }
@media (max-width: 480px) { @media (max-width: 480px) {
@ -50,6 +52,7 @@ const popupDemoStyle = css`
`; `;
storiesOf('Local Overlay System|Popup', module) storiesOf('Local Overlay System|Popup', module)
.addDecorator(withKnobs)
.add( .add(
'Button popup', 'Button popup',
() => html` () => html`
@ -57,36 +60,73 @@ storiesOf('Local Overlay System|Popup', module)
${popupDemoStyle} ${popupDemoStyle}
</style> </style>
<div class="demo-box"> <div class="demo-box">
<lion-popup position="right"> <lion-popup .placementConfig="${{ placement: 'top' }}">
<div slot="content" class="popup">hey there</div> <div slot="content" class="popup">Hello there!</div>
<lion-button slot="invoker">Popup</lion-button> <lion-button slot="invoker">Popup</lion-button>
</lion-popup> </lion-popup>
</div> </div>
`, `,
) )
.add( .add(
'positions', 'placements',
() => html` () => html`
<style> <style>
${popupDemoStyle} ${popupDemoStyle}
</style> </style>
<div class="demo-box_positions"> <div class="demo-box_placements">
<lion-popup position="top"> <lion-popup .placementConfig="${{ placement: 'top' }}">
<div slot="content" class="popup">Its top position</div> <div slot="content" class="popup">Its top placement</div>
<lion-button slot="invoker">Top</lion-button> <lion-button slot="invoker">Top</lion-button>
</lion-popup> </lion-popup>
<lion-popup position="right"> <lion-popup .placementConfig="${{ placement: 'right' }}">
<div slot="content" class="popup">Its right position</div> <div slot="content" class="popup">Its right placement</div>
<lion-button slot="invoker">Right</lion-button> <lion-button slot="invoker">Right</lion-button>
</lion-popup> </lion-popup>
<lion-popup position="bottom"> <lion-popup .placementConfig="${{ placement: 'bottom' }}">
<div slot="content" class="popup">Its bottom position</div> <div slot="content" class="popup">Its bottom placement</div>
<lion-button slot="invoker">Bottom</lion-button> <lion-button slot="invoker">Bottom</lion-button>
</lion-popup> </lion-popup>
<lion-popup position="left"> <lion-popup .placementConfig="${{ placement: 'left' }}">
<div slot="content" class="popup">Its left position</div> <div slot="content" class="popup">Its left placement</div>
<lion-button slot="invoker">Left</lion-button> <lion-button slot="invoker">Left</lion-button>
</lion-popup> </lion-popup>
</div> </div>
`, `,
)
.add(
'Override placement configuration',
() => html`
<style>
${popupDemoStyle}
</style>
<p>Use the Storybook Knobs to dynamically change the placement configuration!</p>
<div class="demo-box">
<lion-popup
.placementConfig="${object('Placement Configuration', {
placement: 'bottom-start',
positionFixed: true,
modifiers: {
keepTogether: {
enabled: true /* Prevents detachment of content element from reference element */,
},
preventOverflow: {
enabled: true /* disables shifting/sliding behavior on secondary axis */,
padding: 16 /* when enabled, this is the viewport-margin for shifting/sliding */,
},
flip: {
boundariesElement: 'viewport',
padding: 4 /* viewport-margin for flipping on primary axis */,
},
offset: {
enabled: true,
offset: `0, 4px` /* horizontal and vertical margin (distance between popper and referenceElement) */,
},
},
})}"
>
<div slot="content" class="popup">${text('Content text', 'Hello, World!')}</div>
<lion-button slot="invoker">${text('Invoker text', 'Click me!')}</lion-button>
</lion-popup>
</div>
`,
); );

View file

@ -1,4 +1,4 @@
import { expect, fixture, html } from '@open-wc/testing'; import { expect, fixture, html, aTimeout } from '@open-wc/testing';
import '../lion-popup.js'; import '../lion-popup.js';
@ -25,6 +25,7 @@ describe('lion-popup', () => {
const eventOnClick = new Event('click'); const eventOnClick = new Event('click');
invoker.dispatchEvent(eventOnClick); invoker.dispatchEvent(eventOnClick);
await el.updateComplete; await el.updateComplete;
await aTimeout();
expect(el.querySelector('[slot="content"]').style.display).to.be.equal('inline-block'); expect(el.querySelector('[slot="content"]').style.display).to.be.equal('inline-block');
invoker.dispatchEvent(eventOnClick); invoker.dispatchEvent(eventOnClick);
await el.updateComplete; await el.updateComplete;
@ -44,6 +45,21 @@ describe('lion-popup', () => {
await el.updateComplete; await el.updateComplete;
expect(el.querySelector('strong')).to.not.be.undefined; expect(el.querySelector('strong')).to.not.be.undefined;
}); });
it('should respond to dynamically changing the placementConfig', async () => {
const el = await fixture(html`
<lion-popup>
<div slot="content" class="popup">Hey there</div>
<lion-button slot="invoker">Popup button</lion-button>
</lion-popup>
`);
await el._controller.show();
expect(el._controller._popper.options.placement).to.equal('top');
el.placementConfig = { placement: 'left' };
// placementConfig setter calls updateConfig method which recreates popper instance and this process is async..
await aTimeout();
expect(el._controller._popper.options.placement).to.equal('left');
});
}); });
describe('Accessibility', () => { describe('Accessibility', () => {