lion/packages/overlays/src/OverlayMixin.js
Thijs Louisse 364f185ad8 feat(overlays): release new overlay system
Co-authored-by: Thomas Allmer <Thomas.Allmer@ing.com>
Co-authored-by: Joren Broekema <Joren.Broekema@ing.com>
Co-authored-by: Mikhail Bashkirov <Mikhail.Bashkirov@ing.com>
2019-10-10 17:14:24 +02:00

143 lines
4.1 KiB
JavaScript

import { render, dedupeMixin } from '@lion/core';
/**
* @type {Function()}
* @polymerMixin
* @mixinFunction
*/
export const OverlayMixin = dedupeMixin(
superclass =>
// eslint-disable-next-line no-shadow
class OverlayMixin extends superclass {
static get properties() {
return {
opened: {
type: Boolean,
reflect: true,
},
popperConfig: Object,
};
}
get opened() {
return this._overlayCtrl.isShown;
}
set opened(show) {
this._opened = show; // mainly captured for sync on connectedCallback
if (this._overlayCtrl) {
this.__syncOpened();
}
}
__syncOpened() {
if (this._opened) {
this._overlayCtrl.show();
} else {
this._overlayCtrl.hide();
}
}
get popperConfig() {
return this._popperConfig;
}
set popperConfig(config) {
this._popperConfig = {
...this._popperConfig,
...config,
};
this.__syncPopper();
}
__syncPopper() {
if (this._overlayCtrl) {
this._overlayCtrl.updatePopperConfig(this._popperConfig);
}
}
connectedCallback() {
if (super.connectedCallback) {
super.connectedCallback();
}
this._createOverlay();
this.__syncOpened();
this.__syncPopper();
}
firstUpdated(c) {
super.firstUpdated(c);
this._createOutletForLocalOverlay();
}
updated(c) {
super.updated(c);
if (this.__managesOverlayViaTemplate) {
this._renderOverlayContent();
}
}
_renderOverlayContent() {
render(this._overlayTemplate(), this.__contentParent, {
scopeName: this.localName,
eventContext: this,
});
}
/**
* @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);
}
_createOverlay() {
let contentNode;
if (this.__managesOverlayViaTemplate) {
this.__contentParent = document.createElement('div');
this._renderOverlayContent();
contentNode = this.__contentParent.firstElementChild;
} else {
contentNode = this._overlayContentNode;
}
// 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 });
}
/**
* @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';
}
/**
* @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
* @returns {OverlayController}
*/
// eslint-disable-next-line
_defineOverlay({ contentNode, invokerNode }) {}
},
);