fix(overlays): fix contentNodes for local and global overlays
Co-authored by: Thijs Louisse <Thijs.Louisse@ing.com>
This commit is contained in:
parent
9e39c6c19c
commit
733991d92c
15 changed files with 178 additions and 56 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ TODO:
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bottomsheet Controller
|
### BottomSheetController
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// **********************************************************************************************
|
// **********************************************************************************************
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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', () => {
|
||||||
|
|
|
||||||
|
|
@ -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', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue