@dbp-toolkit/common
Version:
You can provide attributes (e.g. `global-name`) for components inside the provider:
224 lines (198 loc) • 8.46 kB
JavaScript
import {html, css} from 'lit';
import {createInstance} from './i18n';
import * as commonStyles from './styles.js';
import {Icon} from './icon';
import {ScopedElementsMixin} from './scoped/ScopedElementsMixin.js';
import dialogPolyfill from 'dialog-polyfill';
import DBPLitElement from './dbp-lit-element';
import {LangMixin} from './lang-mixin.js';
export class Modal extends LangMixin(ScopedElementsMixin(DBPLitElement), createInstance) {
constructor() {
super();
/** @type {HTMLDialogElement} */
this.modalDialog = null;
/** @type {string} */
this.modalId = 'dbp-modal-id';
/** @type {string} */
this.title = '';
/** @type {boolean} */
this.stickyFooter = false;
/** @type {number} */
this.modalPaddingTopDefault;
}
static get properties() {
return {
modalId: {type: String, attribute: 'modal-id'},
title: {type: String},
stickyFooter: {type: Boolean, attribute: 'sticky-footer'},
};
}
connectedCallback() {
super.connectedCallback();
}
updated(changedProperties) {
super.updated(changedProperties);
}
firstUpdated() {
this.modalDialog = /** @type {HTMLDialogElement} */ (this._('#' + this.modalId));
dialogPolyfill.registerDialog(this.modalDialog);
// Save default value of padding top changed when adding/removing notifications
this.modalPaddingTopDefault = parseInt(
window.getComputedStyle(this.modalDialog).paddingTop,
);
this.modalDialog.addEventListener('close', (event) => {
// Re allow scrolling the page when dialog is closed
const htmlElement = this.modalDialog.ownerDocument.documentElement;
htmlElement.style.removeProperty('overflow');
const customEvent = new CustomEvent('dbp-modal-closed', {
detail: {id: this.modalId},
bubbles: true,
composed: true,
});
this.dispatchEvent(customEvent);
});
this.addEventListener('dbp-notification-added', (e) => {
const notificationEvent = /** @type {CustomEvent} */ (e);
const notificationId = notificationEvent.detail.targetNotificationId;
this.updateModalNotificationPadding(notificationId);
});
this.addEventListener('dbp-notification-removed', (e) => {
const notificationEvent = /** @type {CustomEvent} */ (e);
const notificationId = notificationEvent.detail.targetNotificationId;
this.updateModalNotificationPadding(notificationId);
});
}
async updateModalNotificationPadding(notificationId) {
const notificationSlot = this.querySelector('[slot="header"]');
if (!notificationSlot) {
return;
}
const notificationComponent = notificationSlot.querySelector('#' + notificationId);
if (!notificationComponent || !notificationComponent.shadowRoot) {
return;
}
/** @type {HTMLElement} */
const notificationContainer =
notificationComponent.shadowRoot.querySelector('#notification-container');
// Get height of notification and add as padding top to the top of the modal
if (notificationContainer) {
// Wait until next frame to ensure styles are applied
await new Promise((resolve) => requestAnimationFrame(resolve));
const modalPosition = this.modalDialog.getBoundingClientRect();
const modalPaddingTopCurrent = parseInt(
window.getComputedStyle(this.modalDialog).getPropertyValue('padding-top'),
);
const modalPaddingTopDefault = this.modalPaddingTopDefault;
const notificationContainerHeight =
notificationContainer.offsetHeight + modalPaddingTopDefault;
// Until there is more than 1 notification place over the modal add padding top and translate the modal up
// If the padding top is greater than the notification container height, reduce the padding top and translate the modal up
if (modalPosition.top > 150 || modalPaddingTopCurrent > notificationContainerHeight) {
this.modalDialog.style.setProperty(
'--dbp-modal-padding-top',
notificationContainerHeight + 'px',
);
this.modalDialog.style.setProperty(
'--dbp-modal-translate-y',
notificationContainerHeight / -2 + 'px',
);
}
}
}
static get scopedElements() {
return {
'dbp-icon': Icon,
};
}
isOpen() {
return this.modalDialog.open;
}
open() {
// Don't open the dialog if it is already open
if (this.modalDialog.open) {
return;
}
// Prevent scrolling the page when dialog is open
const htmlElement = this.modalDialog.ownerDocument.documentElement;
htmlElement.style.overflow = 'hidden';
this.modalDialog.showModal();
}
close() {
this.modalDialog.close();
// Remove all notifications if modal is closed
const notificationSlot = this.querySelector('[slot="header"]');
if (notificationSlot) {
const notificationComponent = notificationSlot.querySelector('dbp-notification');
if (notificationComponent && notificationComponent.shadowRoot) {
notificationComponent.removeAllNotifications();
}
}
// Reset modal padding and translation
this.modalDialog.style.setProperty(
'--dbp-modal-padding-top',
this.modalPaddingTopDefault + 'px',
);
this.modalDialog.style.removeProperty('--dbp-modal-translate-y');
}
static get styles() {
// language=css
return css`
${commonStyles.getNativeModalDialogCSS()}
${commonStyles.getNativeModalDialogPrintCSS()}
`;
}
render() {
const i18n = this._i18n;
return html`
<dialog
class="modal"
id="${this.modalId}"
autofocus
role="alertdialog"
aria-describedby="modal-content"
aria-labelledby="modal-title">
<div class="modal-container">
<header class="modal-header">
<div class="header-top">
<slot name="title">
<h3 class="modal-title" id="modal-title">${this.title}</h3>
</slot>
<button
title="${i18n.t('dbp-modal.close')}"
class="modal-close"
aria-label="${i18n.t('dbp-modal.close')}"
@click="${() => {
this.close();
}}">
<dbp-icon
title="${i18n.t('dbp-modal.close')}"
name="close"
class="close-icon"></dbp-icon>
</button>
</div>
<div class="header-bottom">
<slot name="header"></slot>
</div>
</header>
<main class="modal-content" id="modal-content">
<slot name="content"></slot>
${!this.stickyFooter
? html`
<footer class="modal-footer modal-footer--sticky">
<slot name="footer"></slot>
</footer>
`
: ''}
</main>
${this.stickyFooter
? html`
<footer class="modal-footer">
<slot name="footer"></slot>
</footer>
`
: ''}
</div>
</dialog>
`;
}
}