@postnord/web-components
Version:
PostNord Web Components
119 lines (115 loc) • 7.35 kB
JavaScript
/*!
* Built with Stencil
* By PostNord.
*/
import { r as registerInstance, c as createEvent, g as getElement, f as forceUpdate, h, a as Host } from './index-5606614b.js';
import { c as close } from './close-9e832820.js';
const pnModalCss = "pn-modal .pn-modal{position:fixed;bottom:round(50%, 1px);left:round(50%, 1px);width:round(95%, 1px);max-width:45em;max-height:round(85vh, 1px);border:0.0625em solid #f3f2f2;border-radius:0.5em;transform:translate(-50%, 60%);box-shadow:0 0.25em 0.875em rgba(0, 0, 0, 0.18), 0 1.625em 3.5em rgba(0, 0, 0, 0.22);background-color:#ffffff;z-index:999;pointer-events:none;opacity:0;visibility:hidden;overflow-y:auto;transition:visibility 0s linear 0.55s, opacity 0.25s 0.15s, transform 0.55s cubic-bezier(0.7, 0, 0.3, 1)}pn-modal[data-open] .pn-modal{visibility:visible;opacity:1;pointer-events:auto;transition:visibility 0s linear 0s, opacity 0.55s, transform 0.55s cubic-bezier(0.7, 0, 0.3, 1);transform:translate(round(-50%, 1px), round(50%, 1px))}.pn-modal>.pn-modal-content{width:100%;height:round(100%, 1px);padding:1.5em}.pn-modal>.pn-modal-close-button{position:absolute;right:1em;top:1em}pn-modal .pn-modal>[slot=buttons]{position:static;width:100%;background-color:#ffffff;border-top:0.0625em solid #d3cecb;padding:0.5em 1em;display:flex;justify-content:flex-end;flex-wrap:wrap-reverse}pn-modal .pn-modal>[slot=buttons]>*{margin:0.5em}pn-modal>.pn-modal-backdrop{position:fixed;width:round(120vw, 1px);height:round(120vh, 1px);top:round(-10vh, 1px);left:round(-10vw, 1px);background-color:#000000;opacity:0;z-index:9;visibility:hidden;transition:visibility 0s linear 0.3s, opacity 0.3s}pn-modal[data-open] .pn-modal-backdrop{opacity:0.55;visibility:visible;transition:visibility 0s linear 0s, opacity 0.3s}@media screen and (max-width: 30em){pn-modal .pn-modal{width:round(100%, 1px);max-height:round(95vh, 1px);bottom:0;transform:translate(round(-50%, 1px), round(10%, 1px));border-radius:0.5em 0.5em 0 0}pn-modal[data-open] .pn-modal{transform:translate(round(-50%, 1px), 0)}pn-modal .pn-modal>[slot=buttons]{justify-content:center}pn-modal .pn-modal>[slot=buttons]>*{flex:1 0 auto}}@supports (position: sticky){pn-modal .pn-modal>[slot=buttons]{position:sticky;bottom:0;left:0;z-index:2}pn-modal .pn-modal>.pn-modal-close-button{position:sticky;left:calc(100% - 3em);margin:1em 1em 0 0;z-index:10}pn-modal .pn-modal>.pn-modal-content{margin-top:-3em}}";
const PnModalStyle0 = pnModalCss;
const PnModal = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.close = createEvent(this, "close", 7);
this.focusableEls = undefined;
this.open = false;
}
mo;
untabbable;
elToFocus;
handleFocus = this.focusHandler.bind(this);
handleBlur = this.blurHandler.bind(this);
handleEsc = this.escHandler.bind(this);
handleGlobalClick = this.globalClickHandler.bind(this);
get hostElement() { return getElement(this); }
handleOpen() {
if (this.open) {
this.addEventListeners();
}
else {
this.removeEventListeners();
this.elToFocus = null;
this.close.emit(this.open);
}
}
/** Event fired when the modal is closed, either by clicking on the dark background or by clicking on the close button. */
close;
componentDidLoad() {
if (this.mo)
this.mo.disconnect();
this.mo = new MutationObserver(() => {
forceUpdate(this.hostElement);
this.setFocusableElements();
});
this.mo.observe(this.hostElement.querySelector('.pn-modal'), { childList: true, subtree: true });
// If the modal is opened when the page loads we still want to register the events.
if (this.open)
this.handleOpen();
this.setFocusableElements();
}
toggleOpen(state) {
this.open = state ?? !this.open;
}
setFocusableElements() {
// This place is where I see the most coming changes/bugs taking place.
requestAnimationFrame(() => {
this.focusableEls = Array.from(this.hostElement.querySelectorAll('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"]):not(.pn-modal-backdrop)'));
this.untabbable = Array.from(this.hostElement.querySelectorAll('[tabindex="-1"]'));
});
}
addEventListeners() {
const root = this.hostElement.getRootNode();
root.addEventListener('focusin', this.handleFocus);
root.addEventListener('focusout', this.handleBlur);
root.addEventListener('keydown', this.handleEsc);
// Adding RAF to ensure clicks aren't registered before the modal has opened.
requestAnimationFrame(() => document.addEventListener('pointerdown', this.handleGlobalClick));
}
removeEventListeners() {
const root = this.hostElement.getRootNode();
root.removeEventListener('focusin', this.handleFocus);
root.removeEventListener('focusout', this.handleBlur);
root.removeEventListener('keydown', this.handleEsc);
document.removeEventListener('pointerdown', this.handleGlobalClick);
}
focusHandler(e) {
const target = e.composedPath()[0];
if ((!this.focusableEls.includes(target) && !this.untabbable.includes(target)) ||
target.classList.contains('pn-modal-backdrop')) {
if (this.elToFocus) {
this.elToFocus.focus();
return;
}
this.focusableEls[0].focus();
}
}
blurHandler(e) {
const target = e.composedPath?.()[0];
const index = this.focusableEls.indexOf(target);
const numberOfEls = this.focusableEls.length - 1;
if (index === 0)
this.elToFocus = this.focusableEls[numberOfEls];
if (index === numberOfEls)
this.elToFocus = this.focusableEls[0];
}
escHandler({ code }) {
if (code === 'Escape')
this.toggleOpen(false);
}
globalClickHandler(e) {
const target = e.composedPath?.()[0];
if (!this.hostElement.contains(target) || target.classList.contains('pn-modal-backdrop')) {
// This is to prevent the focus and blur events from being triggered when closing the modal.
e.preventDefault();
this.toggleOpen(false);
}
}
render() {
return (h(Host, { key: 'e147efddf99cb12726295253e89ec8f83c16d6a1', "data-open": this.open }, h("div", { key: '9ad10f6e7fdb2445d6b1a99801bd416cc77df976', class: "pn-modal-backdrop", tabindex: "0" }), h("div", { key: '479ee53eac130748bc33d2b49da57fe183a26c34', class: "pn-modal" }, h("pn-button", { key: 'fe7d40eed5b260949e889ed172d77311748873e9', small: true, class: "pn-modal-close-button", icon: close, iconOnly: true, arialabel: "close", appearance: "light", type: "button", onPnClick: () => this.toggleOpen() }), h("div", { key: '6f0a21860e2eaa16f01b87ac11cce3095768e0d9', class: "pn-modal-content" }, h("slot", { key: 'd15335e6f44b2a4b4d394bd3779e65ab2f3d7d4f' })), h("slot", { key: '24b457f92a61552a0cdd375c97d2a3cc1106013d', name: "buttons" }))));
}
static get watchers() { return {
"open": ["handleOpen"]
}; }
};
PnModal.style = PnModalStyle0;
export { PnModal as pn_modal };
//# sourceMappingURL=pn-modal.entry.js.map