UNPKG

@conectate/ct-dialog

Version:

HTML dialog made with Web Components

514 lines (503 loc) 16.3 kB
/** @license Copyright (c) 2020 Herberth Obregón. All rights reserved. This code may only be used under the BSD style license found at https://open.grupoconectate.com/LICENSE.txt The complete set of authors may be found at https://open.grupoconectate.com/AUTHORS.txt The complete set of contributors may be found at https://open.grupoconectate.com/CONTRIBUTORS.txt Code distributed by Herberth Obregón as part of the Conectate Open Source Project is also subject to an additional IP rights grant found at https://open.grupoconectate.com/PATENTS.txt */ var CtDialog_1; import { __decorate } from "tslib"; import { getClient, sleep } from "@conectate/ct-helpers"; import { CtLit, css, customElement, html, property, state } from "@conectate/ct-lit"; let ctDialogs = []; // @ts-ignore window.ctDialogs != null || (window.ctDialogs = []); // @ts-ignore ctDialogs = window.ctDialogs; let dialogID = 0; let toDelete = null; export function showCtDialog(el, id, history) { let ctDialog; // Inserto el ID del dialogo que voy a mostrar ctDialog = new CtDialog(); ctDialog.dialogID = id || `${dialogID++}`; ctDialogs.push({ id: ctDialog.dialogID, dialog: ctDialog }); history && (ctDialog.history = history); ctDialog.show(); ctDialog.element = el; return ctDialog; } export function closeCtDialog(id) { return new Promise(async (resolve) => { let m = document.querySelectorAll("ct-dialog"); for (let mod = 0; mod < m.length; mod++) { let modal = m[mod]; if (modal == null) return; if (id == modal || id == null) { await modal.close(); } } resolve(0); }); } // @ts-ignore window.showCtDialog = showCtDialog; // @ts-ignore window.closeCtDialog = closeCtDialog; export var DialogSizePreferences; (function (DialogSizePreferences) { /** Para pantalla completa */ DialogSizePreferences[DialogSizePreferences["fullsreen"] = 0] = "fullsreen"; /** Para que se muestre al 80% de la ventada y 21cm de ancho */ DialogSizePreferences[DialogSizePreferences["fullsize"] = 1] = "fullsize"; })(DialogSizePreferences || (DialogSizePreferences = {})); let _closeViaPopState = async (e) => { // Si el dialogo que se esta cerrando es el mismo que el que se abrio, lo cierro [REF.1] if (ctDialogs.length == 0) return; let dialogPop = ctDialogs[ctDialogs.length - 1]; if (dialogPop.dialog) { // Lo elimino de la lista de dialogos if (toDelete == dialogPop.dialog) { toDelete = null; dialogPop.dialog.destroy(); } else { await dialogPop.dialog.close(e, "popstate"); } } }; let _clseDialogESC = (e) => { if (e.key === "Escape") { if (ctDialogs.length == 0) return; let dialogPop = ctDialogs[ctDialogs.length - 1]; // if (dialogPop.dialog.interactiveDismissDisabled) return; dialogPop.dialog.close(e, "keyup"); } }; window.addEventListener("popstate", _closeViaPopState, false); document.addEventListener("keyup", _clseDialogESC, false); /** * @cssProp --ct-dialog-width - Ancho del dialogo * @cssProp --ct-dialog-height - Alto del dialogo * @fires close - Se dispara cuando se cierra el dialogo * @fires open - Se dispara cuando se abre el dialogo */ let CtDialog = class CtDialog extends CtLit { static { CtDialog_1 = this; } /** En algunas version */ static { this.checkForCriOS = false; } static { this.hiddenOverflow = document.body; } static { this.styles = [ css ` @keyframes in-modalFadeEffect { from { opacity: 0; } to { opacity: 1; } } @keyframes in-scale-ct { from { transform: scale(0.1); } to { transform: scale(1); } } @keyframes in-scale-cupertino { from { transform: scale(1.2); } to { transform: scale(1); } } @keyframes in-slide-right { from { transform: translateX(100%); transition-timing-function: ease-in-out; } to { transform: translateX(0); } } @keyframes in-slide-left { from { transform: translateX(-100%); transition-timing-function: ease-in-out; } to { transform: translateX(0); } } @keyframes in-bottom-sheet { from { transform: translateY(100%); transition-timing-function: ease-in-out; } to { transform: translateY(0); } } `, css ` :host { display: flex; flex-direction: column; align-items: center; justify-content: center; position: fixed; z-index: var(--zi-modal, 110); left: env(safe-area-inset-right, 0); top: env(safe-area-inset-top, 0); right: env(safe-area-inset-right, 0); bottom: env(safe-area-inset-bottom, 0); overflow-y: auto; overflow-x: hidden; animation: in-modalFadeEffect 0.2s; font-family: "Roboto", sans-serif !important; box-align: center; box-orient: vertical; box-sizing: border-box; } :host([type="bottom-sheet"]) { justify-content: flex-end; } .overlay { position: fixed; left: env(safe-area-inset-right, 0); top: env(safe-area-inset-top, 0); right: env(safe-area-inset-right, 0); bottom: env(safe-area-inset-bottom, 0); background-color: rgba(0, 0, 0, 0.65); animation: in-modalFadeEffect 0.2s; transition: backdrop-filter 200ms; z-index: -1; } /* @supports (backdrop-filter: blur(12px)) { .overlay { backdrop-filter: blur(12px); } } */ .anim-normal { animation: in-scale-cupertino 0.25s; } .anim-cupertino { animation: in-scale-cupertino 0.25s; } .anim-slide-right { animation: in-slide-right 0.5s; } .anim-slide-left { animation: in-slide-left 0.5s; } .anim-bottom-sheet { animation: in-bottom-sheet 0.5s; } .no-anim { animation: none; } .c { display: block; position: relative; max-height: var(--ct-dialog-height, 80%); max-height: var(--ct-dialog-height, 85dvh); max-width: var(--ct-dialog-width, 25cm); width: 100%; /* height: max-content; */ overflow: auto; margin: 0; box-sizing: border-box; z-index: 200; } ` ]; } get element() { return this._element; } set element(val) { let old = this._element; this._element = val; this.requestUpdate("element", old); // Calc for dvh let client = getClient(); let oldChrome = client.browser == "chrome" && client.browserVersion < 108; let oldSafari = client.browser == "safari" && client.browserVersion < 16.4; let oldFirefox = client.browser == "firefox"; /** * [FIX] * [Windows Vista] Chrome v75 height 0px; * [MacOs] Overflow fix */ this.updateComplete.then(async () => { await sleep(300); let bodyY = Math.min(document.body.getBoundingClientRect().height, window.innerHeight); let elementY = this._element.offsetHeight; // console.log('bodyY', bodyY, 'elementY', elementY, elementY / bodyY); if (bodyY > elementY) { // Reviso si es menor al 5% de la pantalla if ((elementY / bodyY) * 100 < 5) { console.warn("[ct-dialog] El elemento no es visible"); if (this._element) this._element.style.height = `var(--ct-dialog-height, ${Math.floor(bodyY * 0.8)}px)`; } else if ((elementY / bodyY) * 100 >= 80) { // console.warn("El elemento esta desbordado"); if (this._element && !this.preferences.includes(DialogSizePreferences.fullsreen) && (oldChrome || oldSafari || oldFirefox)) { this._element.style.height = `var(--ct-dialog-height, ${Math.floor(bodyY * 0.8)}px)`; } } } }); // if (getClient().os == "ios") { // this.updateComplete.then(async () => { // if (this._element) this._element.style.borderRadius = "0px"; // }); // } } render() { return html ` ${this.getStylesPref(this.preferences)} <div class="overlay" @click="${(e) => { e.stopPropagation(); if (this.interactiveDismissDisabled) return; this.close(e, "click"); }}" ></div> ${this.element} `; } getStylesPref(pref) { return pref.map(i => { switch (i) { case DialogSizePreferences.fullsreen: return html ` <style> .c { max-height: 92% !important; max-height: 100dvh; max-width: 100vw !important; max-width: 100dvw !important; } </style> `; } }); } disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("popstate", this._closeViaPopState, false); document.removeEventListener("keyup", this._clseDialogESC, false); } computeAnimation(anim) { switch (anim) { case "alert": return "anim-normal"; case "cupertino": return "anim-cupertino"; case "slide-left": return "anim-slide-left"; case "slide-right": return "anim-slide-right"; case "bottom-sheet": return "anim-bottom-sheet"; } } constructor() { super(); this.interactiveDismissDisabled = false; this.role = "alert"; this.type = "alert"; this.ariaModal = "true"; // Vars this.disableHistoryAPI = false; this.preferences = []; this.mappingContainer = new Promise(resolve => { this.resolveMapping = resolve; }); } firstUpdated() { // Checkeo que no sea Chrome de iOS porque esa mierda no sirve aquí 😡 if (CtDialog_1.checkForCriOS && navigator.userAgent.match(/CriOS\/([0-9\.]+)/)) this.disableHistoryAPI = true; if (this.history == null) this.history = { title: document.title, href: window.location.href }; // Crea una entrada en el History API identica que solo va a servir para usar 'Atras' en Moviles y cerrar el dialogo if (!this.disableHistoryAPI) window.history.pushState({ dialogID: this.dialogID }, this.history.title, this.history.href); this.resolveMapping(); if (this.element?.classList) { this.element.classList.add("c"); this.element.classList.add(this.computeAnimation(this.type)); } } enableHistoryAPI(value = true) { this.disableHistoryAPI = !value; return this; } fullscreenMode() { this.preferences = [...this.preferences, DialogSizePreferences.fullsreen]; return this; } fullsizeMode() { this.preferences = [...this.preferences, DialogSizePreferences.fullsize]; return this; } setAnimation(anim) { this.type = anim; return this; } show() { document.body.appendChild(this); if (CtDialog_1.hiddenOverflow) { CtDialog_1.hiddenOverflow.style.overflow = "hidden"; } } async waitForDefined(param, timeout = 10_000) { while (param() == undefined) { await new Promise(resolve => setTimeout(resolve, 200)); timeout -= 200; if (timeout <= 0) return false; } return param(); } close(e, type) { // Este dialog lo elimino de las lista de dialogos return new Promise(async (resolve) => { let finish = async () => { if (!document.body.contains(this)) { console.warn(`dialogID ya no se encuentra en el DOM`, this); resolve(e); return; } // Si lo cerre manual o por ESC, elimino la entrada del History API. Como ya se eliminó el dialogo de la lista, `this.close()` no se ejecutará [REF.1] if (type != "popstate" && !this.disableHistoryAPI) { toDelete = this; window.history.back(); } else { this.destroy(); } await sleep(250); resolve(e); }; // espero que haga el mapping en el container await this.mappingContainer; let index = ctDialogs.findIndex(d => d.dialog == this); let isLast = index == ctDialogs.length - 1; if (!isLast) { // Cierro todos los dialogos que estan por encima de este for (let i = ctDialogs.length - 1; i > index; i--) { await ctDialogs[i].dialog.close(); } } if (!document.body.animate) { await finish(); return; } let anim = this.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 250, fill: "both" }); this.element?.animate(this.getAnimOut(this.type), { duration: 270, fill: "both" }); anim.onfinish = () => finish(); }); } getAnimOut(type) { switch (type) { case "alert": return [ { transform: "scale(1)", opacity: 1 }, { transform: "scale(1.2)", opacity: 0 } ]; case "cupertino": return [ { transform: "scale(1)", opacity: 1 }, { transform: "scale(0.1)", opacity: 0 } ]; case "slide-left": return [ { transform: "translateX(0)", opacity: 1 }, { transform: "translateX(-100%)", opacity: 0 } ]; case "slide-right": return [ { transform: "translateX(0)", opacity: 1 }, { transform: "translateX(100%)", opacity: 0 } ]; case "bottom-sheet": return [ { transform: "translateY(0)", opacity: 1 }, { transform: "translateY(100%)", opacity: 0 } ]; } } destroy() { ctDialogs.splice(ctDialogs.findIndex(d => d.dialog == this), 1); // Elimino el dialogo document.body.removeChild(this); if (CtDialog_1.hiddenOverflow && ctDialogs.length == 0) { CtDialog_1.hiddenOverflow.style.overflow = ""; } this.dispatchEvent(new CustomEvent("close")); this.dispatchEvent(new CustomEvent("on-close")); } /** * @deprecated Use close() instead. The method will be removed in the next major version. */ closeDialog(e) { return this.close(e, "click"); } static get properties() { return { /** * */ dialogID: { type: String, reflect: true }, /** * Entrada para el History API de tipe {title,href} */ history: { type: Object }, element: { type: Object }, free: { type: Object } }; } }; __decorate([ property({ type: Boolean }) ], CtDialog.prototype, "interactiveDismissDisabled", void 0); __decorate([ property({ type: String, reflect: true }) ], CtDialog.prototype, "role", void 0); __decorate([ property({ type: String, reflect: true }) ], CtDialog.prototype, "type", void 0); __decorate([ property({ type: String, reflect: true, attribute: "aria-modal" }) ], CtDialog.prototype, "ariaModal", void 0); __decorate([ state() ], CtDialog.prototype, "_element", void 0); __decorate([ property({ type: Array }) ], CtDialog.prototype, "preferences", void 0); CtDialog = CtDialog_1 = __decorate([ customElement("ct-dialog") ], CtDialog); export { CtDialog };