fix(overlays): fix contentNodes for local and global overlays

Co-authored by: Thijs Louisse <Thijs.Louisse@ing.com>
This commit is contained in:
Joren Broekema 2019-09-27 12:05:31 +02:00
parent 9e39c6c19c
commit 733991d92c
15 changed files with 178 additions and 56 deletions

View file

@ -26,12 +26,12 @@ const myCtrl = overlays.add(
); );
``` ```
### BottomsheetController ### BottomSheetController
A specific extension of GlobalOverlayController configured to create accessible dialogs at the bottom of the screen. A specific extension of GlobalOverlayController configured to create accessible dialogs at the bottom of the screen.
```js ```js
import { BottomsheetController } from '@lion/overlays'; import { BottomSheetController } from '@lion/overlays';
``` ```
### ModalDialogController ### ModalDialogController

View file

@ -159,7 +159,7 @@ TODO:
}, },
``` ```
### Bottomsheet Controller ### BottomSheetController
```js ```js
{ {

View file

@ -2,7 +2,10 @@ export { DynamicOverlayController } from './src/DynamicOverlayController.js';
export { GlobalOverlayController } from './src/GlobalOverlayController.js'; export { GlobalOverlayController } from './src/GlobalOverlayController.js';
export { globalOverlaysStyle } from './src/globalOverlaysStyle.js'; export { globalOverlaysStyle } from './src/globalOverlaysStyle.js';
export { LocalOverlayController } from './src/LocalOverlayController.js'; export { LocalOverlayController } from './src/LocalOverlayController.js';
export { BottomsheetController } from './src/BottomsheetController.js'; export { BottomSheetController } from './src/BottomSheetController.js';
export { ModalDialogController } from './src/ModalDialogController.js'; export { ModalDialogController } from './src/ModalDialogController.js';
export { overlays } from './src/overlays.js'; export { overlays } from './src/overlays.js';
export { OverlaysManager } from './src/OverlaysManager.js'; export { OverlaysManager } from './src/OverlaysManager.js';
// deprecated
export { BottomSheetController as BottomsheetController } from './src/BottomSheetController.js';

View file

@ -133,7 +133,7 @@ export class BaseOverlayController {
switchOut() {} switchOut() {}
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
contentTemplateUpdated() {} onContentUpdated() {}
__setupContent(params) { __setupContent(params) {
if (params.contentTemplate && params.contentNode) { if (params.contentTemplate && params.contentNode) {
@ -175,23 +175,15 @@ export class BaseOverlayController {
if (this.isShown) { if (this.isShown) {
render(this.contentTemplate(this.contentData), this.content); render(this.contentTemplate(this.contentData), this.content);
this.__contentNode = this.content.firstElementChild; this.__contentNode = this.content.firstElementChild;
this.contentTemplateUpdated(); this.onContentUpdated();
} else { } else {
render(html``, this.content); render(html``, this.content);
this.__contentNode = undefined; this.__contentNode = undefined;
} }
} }
__showHideViaCss() { // eslint-disable-next-line class-methods-use-this
if (!this.contentNode) { __showHideViaCss() {}
return;
}
if (this.isShown) {
this.contentNode.style.display = 'inline-block';
} else {
this.contentNode.style.display = 'none';
}
}
// TODO: this method has to be removed when EventTarget polyfill is available on IE11 // TODO: this method has to be removed when EventTarget polyfill is available on IE11
__fakeExtendsEventTarget() { __fakeExtendsEventTarget() {

View file

@ -1,6 +1,6 @@
import { GlobalOverlayController } from './GlobalOverlayController.js'; import { GlobalOverlayController } from './GlobalOverlayController.js';
export class BottomsheetController extends GlobalOverlayController { export class BottomSheetController extends GlobalOverlayController {
constructor(params) { constructor(params) {
super({ super({
hasBackdrop: true, hasBackdrop: true,
@ -13,4 +13,9 @@ export class BottomsheetController extends GlobalOverlayController {
...params, ...params,
}); });
} }
onContentUpdated() {
super.onContentUpdated();
this.contentNode.classList.add('global-overlays__overlay--bottom-sheet');
}
} }

View file

@ -85,10 +85,29 @@ export class GlobalOverlayController extends BaseOverlayController {
this.__enableFeatures(); this.__enableFeatures();
} }
contentTemplateUpdated() { onContentUpdated() {
this.contentNode.classList.add('global-overlays__overlay'); this.contentNode.classList.add('global-overlays__overlay');
} }
get contentNode() {
return this.__contentNode;
}
set contentNode(node) {
const wrapper = document.createElement('div');
wrapper.appendChild(node);
this.__contentNode = node;
this.content = wrapper;
this.onContentUpdated();
// setting a contentNode means hide/show with css
this.__showHideMode = 'css';
if (this.isShown === false) {
this.content.style.display = 'none';
}
}
/** /**
* Hides the overlay. * Hides the overlay.
*/ */
@ -115,6 +134,18 @@ export class GlobalOverlayController extends BaseOverlayController {
this.defaultHideDone(); this.defaultHideDone();
} }
__showHideViaCss() {
if (!this.contentNode) {
return;
}
if (this.isShown) {
this.content.style.display = '';
} else {
this.content.style.display = 'none';
}
}
/** /**
* Sets up flags. * Sets up flags.
*/ */

View file

@ -239,6 +239,18 @@ export class LocalOverlayController extends BaseOverlayController {
} }
} }
__showHideViaCss() {
if (!this.contentNode) {
return;
}
if (this.isShown) {
this.contentNode.style.display = 'inline-block';
} else {
this.contentNode.style.display = 'none';
}
}
// ********************************************************************************************** // **********************************************************************************************
// FEATURE - hidesOnOutsideClick // FEATURE - hidesOnOutsideClick
// ********************************************************************************************** // **********************************************************************************************

View file

@ -65,6 +65,10 @@ export const globalOverlaysStyle = css`
align-items: center; align-items: center;
} }
.global-overlays__overlay--bottom-sheet {
width: 100%;
}
.global-overlays.global-overlays--blocking-opened .global-overlays__overlay { .global-overlays.global-overlays--blocking-opened .global-overlays__overlay {
display: none; display: none;
} }

View file

@ -1,24 +1,23 @@
import { storiesOf, html } from '@open-wc/demoing-storybook'; import { storiesOf, html } from '@open-wc/demoing-storybook';
import { css } from '@lion/core'; import { css } from '@lion/core';
import { overlays, BottomsheetController } from '../index.js'; import { overlays, BottomSheetController } from '../index.js';
const bottomsheetDemoStyle = css` const bottomSheetDemoStyle = css`
.demo-overlay { .demo-overlay {
width: 100%;
background-color: white; background-color: white;
border: 1px solid lightgrey; border: 1px solid lightgrey;
text-align: center; text-align: center;
} }
`; `;
storiesOf('Global Overlay System|Bottomsheet', module).add('Default', () => { storiesOf('Global Overlay System|BottomSheet', module).add('Default', () => {
const bottomsheetCtrl = overlays.add( const bottomSheetCtrl = overlays.add(
new BottomsheetController({ new BottomSheetController({
contentTemplate: () => html` contentTemplate: () => html`
<div class="demo-overlay"> <div class="demo-overlay">
<p>Bottomsheet</p> <p>BottomSheet</p>
<button @click="${() => bottomsheetCtrl.hide()}">Close</button> <button @click="${() => bottomSheetCtrl.hide()}">Close</button>
</div> </div>
`, `,
}), }),
@ -26,11 +25,11 @@ storiesOf('Global Overlay System|Bottomsheet', module).add('Default', () => {
return html` return html`
<style> <style>
${bottomsheetDemoStyle} ${bottomSheetDemoStyle}
</style> </style>
<a href="#">Anchor 1</a> <a href="#">Anchor 1</a>
<button <button
@click="${event => bottomsheetCtrl.show(event.target)}" @click="${event => bottomSheetCtrl.show(event.target)}"
aria-haspopup="dialog" aria-haspopup="dialog"
aria-expanded="false" aria-expanded="false"
> >

View file

@ -5,6 +5,7 @@ import {
GlobalOverlayController, GlobalOverlayController,
LocalOverlayController, LocalOverlayController,
DynamicOverlayController, DynamicOverlayController,
BottomSheetController,
} from '../index.js'; } from '../index.js';
import { overlays } from '../src/overlays.js'; import { overlays } from '../src/overlays.js';
@ -17,11 +18,9 @@ const dynamicOverlayDemoStyle = css`
} }
.demo-overlay__global--small { .demo-overlay__global--small {
bottom: 0; height: 400px;
left: 0; width: 100%;
width: 100vw; background: #eee;
height: 90%;
background: #ccc;
} }
.demo-overlay__global--big { .demo-overlay__global--big {
@ -50,7 +49,7 @@ storiesOf('Dynamic Overlay System|Switching Overlays', module)
const ctrl = new DynamicOverlayController(); const ctrl = new DynamicOverlayController();
const global1 = overlays.add( const global1 = overlays.add(
new GlobalOverlayController({ new BottomSheetController({
contentTemplate: () => html` contentTemplate: () => html`
<div class="demo-overlay demo-overlay__global demo-overlay__global--small"> <div class="demo-overlay demo-overlay__global demo-overlay__global--small">
<p>I am for small screens < 600px</p> <p>I am for small screens < 600px</p>

View file

@ -1,6 +1,6 @@
import './global-overlay.stories.js'; import './global-overlay.stories.js';
import './modal-dialog.stories.js'; import './modal-dialog.stories.js';
import './bottomsheet.stories.js'; import './bottom-sheet.stories.js';
import './local-overlay.stories.js'; import './local-overlay.stories.js';
import './local-overlay-placement.stories.js'; import './local-overlay-placement.stories.js';
import './dynamic-overlay.stories.js'; import './dynamic-overlay.stories.js';

View file

@ -181,21 +181,6 @@ export const runBaseOverlaySuite = createCtrlFn => {
}); });
describe('_showHideMode="css" (auto selected with .contentNode)', () => { describe('_showHideMode="css" (auto selected with .contentNode)', () => {
it('hides .contentNode via css on hide', async () => {
const ctrl = createCtrlFn({
contentNode: await fixture('<p>direct node</p>'),
});
await ctrl.show();
expect(ctrl.contentNode).to.be.displayed;
await ctrl.hide();
expect(ctrl.contentNode).not.to.be.displayed;
await ctrl.show();
expect(ctrl.contentNode).to.be.displayed;
});
// do we even want to support contentTemplate? // do we even want to support contentTemplate?
it.skip('hides .contentNode from a .contentTemplate via css on hide', async () => { it.skip('hides .contentNode from a .contentTemplate via css on hide', async () => {
const ctrl = createCtrlFn({ const ctrl = createCtrlFn({

View file

@ -1,9 +1,9 @@
import { expect, html } from '@open-wc/testing'; import { expect, html } from '@open-wc/testing';
import { GlobalOverlayController } from '../src/GlobalOverlayController.js'; import { GlobalOverlayController } from '../src/GlobalOverlayController.js';
import { BottomsheetController } from '../src/BottomsheetController.js'; import { BottomSheetController } from '../src/BottomSheetController.js';
describe('BottomsheetController', () => { describe('BottomSheetController', () => {
let defaultOptions; let defaultOptions;
before(() => { before(() => {
@ -15,11 +15,11 @@ describe('BottomsheetController', () => {
}); });
it('extends GlobalOverlayController', () => { it('extends GlobalOverlayController', () => {
expect(new BottomsheetController(defaultOptions)).to.be.instanceof(GlobalOverlayController); expect(new BottomSheetController(defaultOptions)).to.be.instanceof(GlobalOverlayController);
}); });
it('has correct defaults', () => { it('has correct defaults', () => {
const controller = new BottomsheetController(defaultOptions); const controller = new BottomSheetController(defaultOptions);
expect(controller.hasBackdrop).to.equal(true); expect(controller.hasBackdrop).to.equal(true);
expect(controller.isBlocking).to.equal(false); expect(controller.isBlocking).to.equal(false);
expect(controller.preventsScroll).to.equal(true); expect(controller.preventsScroll).to.equal(true);

View file

@ -154,6 +154,50 @@ describe('GlobalOverlayController', () => {
await ctrl.sync({ isShown: false, data: { text: 'goodbye world' } }); await ctrl.sync({ isShown: false, data: { text: 'goodbye world' } });
expect(getRenderedContainers().length).to.equal(0); expect(getRenderedContainers().length).to.equal(0);
}); });
describe('contentNode instead of a lit template', () => {
it('accepts HTML Element (contentNode) as content', async () => {
const contentNode = await fixture(
html`
<p>my content</p>
`,
);
const ctrl = overlays.add(
new GlobalOverlayController({
contentNode,
}),
);
await ctrl.show();
// container, which contains only the contentNode and nothing more
expect(getRootNode().children.length).to.equal(1);
expect(getRootNode().children[0].classList.contains('global-overlays__overlay-container'))
.to.be.true;
expect(getRootNode().children[0]).to.have.trimmed.text('my content');
// overlay (the contentNode)
expect(getRootNode().children[0].children[0].classList.contains('global-overlays__overlay'))
.to.be.true;
});
it('sets contentNode styling to display flex by default', async () => {
const contentNode = await fixture(
html`
<p>my content</p>
`,
);
const ctrl = overlays.add(
new GlobalOverlayController({
contentNode,
}),
);
await ctrl.show();
expect(
window.getComputedStyle(getRootNode().children[0]).getPropertyValue('display'),
).to.equal('flex');
});
});
}); });
describe('elementToFocusAfterHide', () => { describe('elementToFocusAfterHide', () => {

View file

@ -49,7 +49,7 @@ describe('LocalOverlayController', () => {
it('renders holders for invoker and content', async () => { it('renders holders for invoker and content', async () => {
const invokerNode = await fixture(html` const invokerNode = await fixture(html`
<div role="button" id="invoke">Invoker</div> <div role="button" id="invoker">Invoker</div>
`); `);
const ctrl = new LocalOverlayController({ const ctrl = new LocalOverlayController({
contentTemplate: () => html` contentTemplate: () => html`
@ -63,7 +63,7 @@ describe('LocalOverlayController', () => {
</div> </div>
`); `);
expect(el.querySelector('#invoke').textContent.trim()).to.equal('Invoker'); expect(el.querySelector('#invoker').textContent.trim()).to.equal('Invoker');
await ctrl.show(); await ctrl.show();
expect(el.querySelector('#content').textContent.trim()).to.equal('Content'); expect(el.querySelector('#content').textContent.trim()).to.equal('Content');
}); });
@ -122,6 +122,54 @@ describe('LocalOverlayController', () => {
}); });
}); });
describe('nodes', () => {
it('accepts HTML Elements (contentNode) to render content', async () => {
const invokerNode = await fixture(html`
<div role="button" id="invoker">Invoker</div>
`);
const node = document.createElement('div');
node.innerHTML = '<div id="content">Content</div>';
const ctrl = new LocalOverlayController({
contentNode: node,
invokerNode,
});
const el = await fixture(html`
<div>
${ctrl.invoker} ${ctrl.content}
</div>
`);
expect(el.querySelector('#invoker').textContent.trim()).to.equal('Invoker');
await ctrl.show();
expect(el.querySelector('#content').textContent.trim()).to.equal('Content');
});
it('sets display to inline-block for contentNode by default', async () => {
const invokerNode = await fixture(html`
<div role="button" id="invoker">Invoker</div>
`);
const node = document.createElement('div');
node.innerHTML = '<div id="content">Content</div>';
const ctrl = new LocalOverlayController({
contentNode: node,
invokerNode,
});
const el = await fixture(html`
<div>
${ctrl.invoker} ${ctrl.content}
</div>
`);
await ctrl.show();
const contentWrapper = el.querySelector('#content').parentElement;
expect(contentWrapper.style.display).to.equal('inline-block');
});
});
// Please use absolute positions in the tests below to prevent the HTML generated by // Please use absolute positions in the tests below to prevent the HTML generated by
// the test runner from interfering. // the test runner from interfering.
describe('positioning', () => { describe('positioning', () => {