@scania/tegel
Version:
Tegel Design System
262 lines (257 loc) • 15.9 kB
JavaScript
import { p as proxyCustomElement, H, d as createEvent, h, c as Host } from './p-28ef5186.js';
import { h as hasSlot } from './p-ae110fc2.js';
import { g as generateUniqueId } from './p-11648030.js';
import { d as defineCustomElement$2 } from './p-b390ece5.js';
const modalCss = ".tds-modal{box-sizing:border-box;box-shadow:var(--tds-modal-box-shadow);background-color:var(--background-elevation-layer-02);margin:auto;position:relative;border-radius:var(--radius-narrow);max-height:85vh;overflow-y:auto;pointer-events:auto}.tds-modal *{box-sizing:border-box}.tds-modal::-webkit-scrollbar{width:6px;background-color:transparent}.tds-modal::-webkit-scrollbar-track{background-color:transparent}.tds-modal::-webkit-scrollbar-thumb{background-color:var(--foreground-elements-transparparent-discrete);border-radius:3px}.tds-modal__actions-sticky{overflow:hidden;display:flex;flex-direction:column}.tds-modal__actions-sticky .body{font-family:var(--body-01-font-family);font-size:var(--body-01-font-size);line-height:var(--body-01-line-height);font-weight:var(--body-01-font-weight);letter-spacing:var(--body-01-letter-spacing);text-transform:var(--body-01-text-transform);max-height:calc(85vh - 36px);overflow-y:auto}.tds-modal__actions-sticky slot[name=actions]{bottom:-1px;left:0;right:0;background-color:var(--background-elevation-layer-02);padding:24px 16px 16px;display:flex;gap:16px}.tds-modal__actions-static slot[name=actions]{background-color:var(--background-elevation-layer-02);display:flex;gap:16px;padding:24px 16px 16px}@media (min-width: 320px){.tds-modal-xs{width:100%}.tds-modal-sm{width:100%}.tds-modal-md{width:100%}.tds-modal-lg{width:100%}}@media (min-width: 672px){.tds-modal-xs{width:50%}.tds-modal-sm{width:62.5%}.tds-modal-md{width:75%}.tds-modal-lg{width:100%}}@media (min-width: 1056px){.tds-modal-xs{width:31.25%}.tds-modal-sm{width:43.75%}.tds-modal-md{width:62.5%}.tds-modal-lg{width:75%}}@media (min-width: 1312px){.tds-modal-xs{width:31.25%}.tds-modal-sm{width:37.5%}.tds-modal-md{width:62.5%}.tds-modal-lg{width:75%}}@media (min-width: 1584px){.tds-modal-xs{width:25%}.tds-modal-sm{width:37.5%}.tds-modal-md{width:50%}.tds-modal-lg{width:75%}}@media (max-width: 320px){.tds-modal-md,.tds-modal-lg,.tds-modal-sm{height:100%}.tds-modal-md slot[name=actions]::slotted(*),.tds-modal-lg slot[name=actions]::slotted(*),.tds-modal-sm slot[name=actions]::slotted(*){display:flex}}.header{display:flex;padding:16px;position:sticky;top:0;background-color:var(--background-elevation-layer-02);z-index:1}.header,slot[name=header]::slotted(*){font-family:var(--headline-05-font-family);font-size:var(--headline-05-font-size);line-height:var(--headline-05-line-height);font-weight:var(--headline-05-font-weight);letter-spacing:var(--headline-05-letter-spacing);text-transform:var(--headline-05-text-transform);color:var(--foreground-text-strong);margin:0;flex:1}.body{font-family:var(--body-01-font-family);font-size:var(--body-01-font-size);line-height:var(--body-01-line-height);font-weight:var(--body-01-font-weight);letter-spacing:var(--body-01-letter-spacing);text-transform:var(--body-01-text-transform);color:var(--foreground-text-strong);overflow-y:visible;padding:0 16px 16px}.body::-webkit-scrollbar{width:6px;background-color:transparent}.body::-webkit-scrollbar-track{background-color:transparent}.body::-webkit-scrollbar-thumb{background-color:var(--foreground-elements-transparparent-discrete);border-radius:3px}.tds-modal-backdrop{box-sizing:border-box;position:fixed;top:0;right:0;bottom:0;left:0;background-color:var(--tds-modal-backdrop-background);pointer-events:auto}.tds-modal-backdrop *{box-sizing:border-box}button.tds-modal-close{margin:0 0 auto auto;background-color:transparent;border:0;padding:0;appearance:unset}.tds-modal-close{display:inline-block;height:auto;color:var(--foreground-text-strong);cursor:pointer}.tds-modal-close:focus{outline:2px solid var(--tds-focus-outline-color);box-shadow:0 0 0 1px var(--tds-white);outline-offset:1px;z-index:1}@media (min-width: 320px){.tds-modal-close{margin-left:16px}}@media (min-width: 1056px){.tds-modal-close{margin-left:48px}}.tds-modal-close-btn{display:inline-block;height:auto;background-repeat:no-repeat;cursor:pointer}@media (min-width: 320px){.tds-modal-close-btn{margin-left:16px}}@media (min-width: 1056px){.tds-modal-close-btn{margin-left:48px}}.tds-modal-close-btn svg{fill:var(--foreground-text-strong)}.tds-modal-overflow{overflow:hidden}:host{box-sizing:border-box;position:fixed;top:0;right:0;bottom:0;left:0;padding:0 16px;z-index:700;pointer-events:none}:host *{box-sizing:border-box}:host .tds-modal-close{border:none;background-color:transparent}:host .tds-modal-close-btn{border:none;background-color:transparent}@media (max-width: 320px){:host{padding:0}}:host.show{display:flex}:host.hide{display:none}:host(.show){display:flex}:host(.hide){display:none}";
const TdsModalStyle0 = modalCss;
const TdsModal$1 = /*@__PURE__*/ proxyCustomElement(class TdsModal extends H {
constructor() {
super();
this.__registerHost();
this.__attachShadow();
this.tdsClose = createEvent(this, "tdsClose", 7);
this.tdsOpen = createEvent(this, "tdsOpen", 7);
this.handleClose = (event) => {
const closeEvent = this.tdsClose.emit(event);
this.returnFocusOnClose();
if (closeEvent.defaultPrevented) {
return;
}
this.isShown = false;
};
this.handleShow = () => {
const showEvent = this.tdsOpen.emit();
if (showEvent.defaultPrevented) {
return;
}
this.isShown = true;
};
/** Checks if click on Modal is on overlay, if so it closes the Modal if prevent is not true. */
this.handleOverlayClick = (event) => {
const targetList = event.composedPath();
const target = targetList[0];
if (target.classList[0] === 'tds-modal-close' ||
(target.classList[0] === 'tds-modal-backdrop' && this.prevent === false)) {
this.handleClose(event);
}
};
this.handleReferenceElementClick = (event) => {
if (this.isShown) {
this.handleClose(event);
}
else {
this.handleShow();
}
};
/** Check if there is a referenceElement or selector and adds event listener to them if so. */
this.setShowButton = () => {
var _a;
if (this.selector || this.referenceEl) {
const referenceEl = (_a = this.referenceEl) !== null && _a !== void 0 ? _a : document.querySelector(this.selector);
if (referenceEl) {
this.initializeReferenceElement(referenceEl);
}
}
};
/** Adds an event listener to the reference element that shows/closes the Modal. */
this.initializeReferenceElement = (referenceEl) => {
if (referenceEl) {
referenceEl.addEventListener('click', this.handleReferenceElementClick);
}
};
this.header = undefined;
this.prevent = false;
this.size = 'md';
this.actionsPosition = 'static';
this.selector = undefined;
this.referenceEl = undefined;
this.show = undefined;
this.closable = true;
this.tdsAlertDialog = 'dialog';
this.isShown = false;
this.activeElementIndex = 0;
}
/** Shows the Modal. */
async showModal() {
this.isShown = true;
// Set focus on first element when opened
requestAnimationFrame(() => {
const focusableElements = this.getFocusableElements();
if (focusableElements.length > 0) {
focusableElements[0].focus();
this.activeElementIndex = 0;
}
});
}
/** Closes the Modal. */
async closeModal() {
this.isShown = false;
this.returnFocusOnClose();
}
/** Returns the current open state of the Modal. */
async isOpen() {
return this.isShown;
}
connectedCallback() {
if (this.closable === undefined) {
this.closable = true;
}
if (this.show !== undefined) {
this.isShown = this.show;
}
this.initializeModal();
if (this.header && hasSlot('header', this.host)) {
console.warn("Tegel Modal component: Using both header prop and header slot might break modal's design. Please use just one of them. ");
}
if (!this.selector && !this.referenceEl) {
console.warn('Tegel Modal: Missing focus origin. Please provide either a "referenceEl" or a "selector" to ensure focus returns to the element that opened the modal. If the modal is opened programmatically, this message can be ignored.');
}
}
componentWillLoad() {
this.initializeModal();
}
disconnectedCallback() {
this.cleanupModal();
}
/** Initializes or re-initializes the modal, setting up event listeners. */
async initializeModal() {
this.setDismissButtons();
this.setShowButton();
}
/** Cleans up event listeners and other resources. */
async cleanupModal() {
var _a;
if (this.selector || this.referenceEl) {
const referenceEl = (_a = this.referenceEl) !== null && _a !== void 0 ? _a : document.querySelector(this.selector);
if (referenceEl) {
referenceEl.removeEventListener('click', this.handleReferenceElementClick);
}
}
this.host.querySelectorAll('[data-dismiss-modal]').forEach((dismissButton) => {
dismissButton.removeEventListener('click', this.handleClose);
});
}
returnFocusOnClose() {
var _a;
let referenceElement = (_a = this.referenceEl) !== null && _a !== void 0 ? _a : document.querySelector(this.selector);
if (!referenceElement) {
return; // no element to return focus to
}
const potentialReferenceElements = ['BUTTON', 'A', 'INPUT'];
const isNativeFocusable = potentialReferenceElements.includes(referenceElement.tagName);
if (isNativeFocusable) {
referenceElement.focus();
}
else {
// If referenced element is a custom element eg: tds-button we find the interactive element inside:
const interactiveElement = referenceElement.querySelector(potentialReferenceElements.join(','));
if (interactiveElement) {
interactiveElement.focus();
}
}
}
getFocusableElements() {
const focusableSelectors = [
'a[href]',
'button:not([disabled])',
'textarea:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
].join(',');
const focusableInShadowRoot = Array.from(this.host.shadowRoot.querySelectorAll(focusableSelectors));
const focusableInSlots = Array.from(this.host.querySelectorAll(focusableSelectors));
/** Focusable elements */
return [...focusableInShadowRoot, ...focusableInSlots];
}
handleFocusTrap(event) {
if (event.key === 'Escape' && this.isShown && !this.prevent) {
this.handleClose(event);
return;
}
// Only trap focus if the modal is open
if (!this.isShown)
return;
// We care only about the Tab key
if (event.key !== 'Tab')
return;
const focusableElements = this.getFocusableElements();
// If there are no focusable elements
if (focusableElements.length === 0)
return;
event.preventDefault();
// Going backwards (Shift + Tab) on the first element => move to last
if (event.shiftKey) {
this.activeElementIndex -= 1;
if (this.activeElementIndex === -1) {
this.activeElementIndex = focusableElements.length - 1;
}
}
// // Going forwards (Tab) on the last element => move to first
if (!event.shiftKey) {
this.activeElementIndex += 1;
if (this.activeElementIndex === focusableElements.length) {
this.activeElementIndex = 0;
}
}
const nextElement = focusableElements[this.activeElementIndex];
nextElement.focus();
}
/** Adds an event listener to the dismiss buttons that closes the Modal. */
setDismissButtons() {
this.host.querySelectorAll('[data-dismiss-modal]').forEach((dismissButton) => {
dismissButton.addEventListener('click', this.handleClose);
});
}
render() {
const usesHeaderSlot = hasSlot('header', this.host);
const usesActionsSlot = hasSlot('actions', this.host);
const headerId = this.header ? `tds-modal-header-${generateUniqueId()}` : undefined;
const bodyId = `tds-modal-body-${generateUniqueId()}`;
return (h(Host, { key: '2d39760de58fd957585e4370aa88447d85249eca', role: this.tdsAlertDialog, "aria-modal": "true", "aria-describedby": bodyId, "aria-labelledby": headerId, class: {
show: this.isShown,
hide: !this.isShown,
}, onClick: (event) => this.handleOverlayClick(event) }, h("div", { key: '5eeaf5c29529bf77ddde4892067f13d312d1bba7', class: "tds-modal-backdrop" }), h("div", { key: '8a6ccd911dfec947a809320a0631d5f9904ef78d', class: `tds-modal tds-modal__actions-${this.actionsPosition} tds-modal-${this.size}` }, h("div", { key: 'b7c760ebded4e98319cec75c161002a579316902', id: headerId, class: "header" }, this.header && h("div", { key: 'db14e71913052ba5367ab95a41a366c7f2dea4a3', class: "header-text" }, this.header), usesHeaderSlot && h("slot", { key: '62b394718c78e564fd50e7ff3328e637a63679e6', name: "header" }), this.closable && (h("button", { key: '9ff8ad81be4f72ee6a298a9ba39a1024269d3ec2', class: "tds-modal-close", "aria-label": "close", onClick: (event) => this.handleClose(event) }, h("tds-icon", { key: 'b34b8a55c7f5c65ce7a3fdee6114c66ee4ab82c3', name: "cross", size: "20px" })))), h("div", { key: 'ab04e506dd94c69d8f91fb774fa85f355f3629d4', id: bodyId, class: "body" }, h("slot", { key: 'f4a8e2377b4cae3a9c4fa1032e896b0b0c58a06a', name: "body" })), usesActionsSlot && h("slot", { key: '09137d7c8b81f91dbf419b10c8a085de25df1272', name: "actions" }))));
}
get host() { return this; }
static get style() { return TdsModalStyle0; }
}, [1, "tds-modal", {
"header": [1],
"prevent": [4],
"size": [1],
"actionsPosition": [1, "actions-position"],
"selector": [1],
"referenceEl": [16],
"show": [4],
"closable": [4],
"tdsAlertDialog": [1, "tds-alert-dialog"],
"isShown": [32],
"activeElementIndex": [32],
"showModal": [64],
"closeModal": [64],
"isOpen": [64],
"initializeModal": [64],
"cleanupModal": [64]
}, [[10, "keydown", "handleFocusTrap"]]]);
function defineCustomElement$1() {
if (typeof customElements === "undefined") {
return;
}
const components = ["tds-modal", "tds-icon"];
components.forEach(tagName => { switch (tagName) {
case "tds-modal":
if (!customElements.get(tagName)) {
customElements.define(tagName, TdsModal$1);
}
break;
case "tds-icon":
if (!customElements.get(tagName)) {
defineCustomElement$2();
}
break;
} });
}
defineCustomElement$1();
const TdsModal = TdsModal$1;
const defineCustomElement = defineCustomElement$1;
export { TdsModal, defineCustomElement };