UNPKG

gd-bs

Version:

Bootstrap JavaScript, TypeScript and Web Components library.

410 lines (349 loc) 14 kB
import { IModal, IModalProps, IModalOptions } from "./types"; import { Base } from "../base"; import { ClassNames } from "../classNames"; import { appendContent } from "../common"; import { HTML } from "./templates"; /** * Modal Types */ export enum ModalTypes { Small = 1, Medium = 2, Large = 3, XLarge = 4, Full = 5, FullSmall = 6, FullMedium = 7, FullLarge = 8, FullXLarge = 9 } /** * Modal Classes */ export const ModalClassNames = new ClassNames([ "modal-sm", "", "modal-lg", "modal-xl", "modal-fullscreen", "modal-fullscreen-sm-down", "modal-fullscreen-md-down", "modal-fullscreen-lg-down", "modal-fullscreen-xl-down" ]); /** * Modal * @param props The modal properties. */ class _Modal extends Base<IModalProps> implements IModal { private _autoClose: boolean = null; private _eventCreated: boolean = false; private _options: IModalOptions = null; private _tranisitioningFl: boolean = false; // Constructor constructor(props: IModalProps, template: string = HTML) { super(template, props); // Configure the collapse this.configure(); // Configure the events this.configureEvents(); // Configure the parent this.configureParent(); } // Configure the card group private configure() { // Set the modal attributes this.props.id ? this.el.id = this.props.id : null; this.props.disableFade ? null : this.el.classList.add("fade"); // Update the dialog let dialog = this.el.querySelector(".modal-dialog") as HTMLElement; if (dialog) { // Add the class name, based on the type let className = ModalClassNames.getByType(this.props.type); className ? dialog.classList.add(className) : null; // Update the title this.setTitle(this.props.title); // See if we are hiding the close button if (this.props.hideCloseButton) { // Remove the close button let closeButton = dialog.querySelector(".btn-close") as HTMLElement; closeButton ? closeButton.classList.add("d-none") : null; } } // Set the header appendContent(this.el.querySelector(".modal-header"), this.props.header); // Set the body appendContent(this.el.querySelector(".modal-body"), this.props.body); // Set the footer appendContent(this.el.querySelector(".modal-footer"), this.props.footer); // Get the modal options this._options = this.props.options; if (this._options) { // Set the backdrop if (typeof (this._options.backdrop) === "boolean") { this.el.setAttribute("data-bs-backdrop", this._options.backdrop ? "true" : "false"); } // Set the center option if (dialog && typeof (this._options.centered) === "boolean") { dialog.classList.add("modal-dialog-centered"); } // Set the focus if (typeof (this._options.focus) === "boolean") { this.el.setAttribute("data-bs-focus", this._options.focus ? "true" : "false"); } // Set the keyboard if (typeof (this._options.keyboard) === "boolean") { this.el.setAttribute("data-bs-keyboard", this._options.keyboard ? "true" : "false"); } // Set the scrollable option if (dialog && typeof (this._options.scrollable) === "boolean") { dialog.classList.add("modal-dialog-scrollable"); } // See if we are showing the modal if (this._options.visible) { // Toggle the modal this.toggle(); } } } // Configures the auto-close event private configureAutoCloseEvent() { // See if the event exists if (this._eventCreated) { return; } // Ensure the body exists if (document.body) { // Add a click event to the modal document.body.addEventListener("click", (ev: MouseEvent) => { // See if the auto close flag is set if (this._autoClose) { let elContent = (this.el as HTMLElement).querySelector(".modal-content"); // Do nothing if we are tranisitionsing if (this._tranisitioningFl) { return; } // Do nothing if we clicked within the modal if (ev.composedPath().includes(elContent)) { return; } else { // Get the mouse coordinates let x = ev.clientX; let y = ev.clientY; let elCoordinate = elContent.getBoundingClientRect(); // See if we clicked within the modal if (x <= elCoordinate.right && x >= elCoordinate.left && y <= elCoordinate.bottom && y >= elCoordinate.top) { return; } // Else, see if something was selected else if (x == 0 && y == 0) { return; } } // Close the modal if it's visible if (this.isVisible) { this.toggle(); } } }); // Set the flag this._eventCreated = true; } else { // Add the load event window.addEventListener("load", () => { // Configure the event this.configureAutoCloseEvent(); }); } } // Configure the events private configureEvents() { // Execute the events this.props.onRenderHeader ? this.props.onRenderHeader(this.el.querySelector(".modal-header")) : null; this.props.onRenderBody ? this.props.onRenderBody(this.el.querySelector(".modal-body")) : null; this.props.onRenderFooter ? this.props.onRenderFooter(this.el.querySelector(".modal-footer")) : null; // Get the close button let elClose = this.el.querySelector(".btn-close"); if (elClose) { // Add a click event elClose.addEventListener("click", () => { // Hide the modal this.hide(); }); } // See if the keyboard option is set if (this._options && this._options.keyboard) { // Add a click event (this.el as HTMLElement).addEventListener("keydown", ev => { // See if the escape key was clicked and the modal is visible if (ev.code === "27" && this.isVisible) { // Toggle the modal this.toggle(); } }); } // Set the flag to determine if the modal is sticky this.setAutoClose(this.props.options && typeof (this.props.options.autoClose) === "boolean" ? this.props.options.autoClose : true); } /** * Public Interface */ // Hides the modal hide() { // See if a transition is currently happening if (this._tranisitioningFl) { // Wait for the transition to complete let id = setInterval(() => { // See if the transition is complete if (!this._tranisitioningFl) { // Stop the loop clearInterval(id); // Toggle the modal this.isVisible ? this.toggle() : null; } }, 250); } else { // Toggle the modal this.isVisible ? this.toggle() : null; } } // Returns true if the modal is visible get isVisible() { return this.el.classList.contains("show"); } // Updates the auto close flag setAutoClose(value: boolean) { // Set the flag this._autoClose = value; // Configure the event if we are setting the value this._autoClose ? this.configureAutoCloseEvent() : null; } // Updates the backdrop flag setBackdrop(value: boolean) { // Set the backdrop this.el.setAttribute("data-bs-backdrop", value ? "true" : "false"); } // Updates the visibility of the close button setCloseButtonVisibility(showFl: boolean) { // Get the close button let closeButton = this.el.querySelector(".btn-close") as HTMLElement; if (closeButton) { // See if we are showing the button if (showFl) { // Show the button closeButton.classList.remove("d-none"); } else { // Hide the button closeButton.classList.add("d-none"); } } } // Updates the focus flag setFocus(value: boolean) { // Set the focus if (typeof (this._options.focus) === "boolean") { this.el.setAttribute("data-bs-focus", value ? "true" : "false"); } } // Updates the center option setIsCentered(value: boolean) { // Get the dialog let dialog = this.el.querySelector(".modal-dialog") as HTMLElement; if (dialog) { // Add/Remove the class name dialog.classList[value ? "add" : "remove"]("modal-dialog-centered"); } } // Updates the keyboard flag setKeyboard(value: boolean) { // Set the keyboard if (typeof (this._options.keyboard) === "boolean") { this.el.setAttribute("data-bs-keyboard", value ? "true" : "false"); } } // Updates the scrollable option setScrollable(value: boolean) { // Get the dialog let dialog = this.el.querySelector(".modal-dialog") as HTMLElement; if (dialog) { // Add/Remove the class name dialog.classList[value ? "add" : "remove"]("modal-dialog-scrollable"); } } // Updates the title setTitle(title: string | Element) { // Get the title let elTitle = this.el.querySelector(".modal-title") as HTMLElement; if (elTitle) { // Clear the element while (elTitle.firstChild) { elTitle.removeChild(elTitle.firstChild); } // Append the content appendContent(elTitle, title); } } // Updates the type setType(modalType: number) { let dialog = this.el.querySelector(".modal-dialog") as HTMLElement; // Parse the class names ModalClassNames.parse(className => { // Remove the class names className ? dialog.classList.remove(className) : null; }); // Set the class name let className = ModalClassNames.getByType(modalType); className ? dialog.classList.add(className) : null; } // Shows the modal show() { // See if a transition is currently happening if (this._tranisitioningFl) { // Wait for the transition to complete let id = setInterval(() => { // See if the transition is complete if (!this._tranisitioningFl) { // Stop the loop clearInterval(id); // Toggle the modal this.isVisible ? null : this.toggle(); } }, 250); } else { // Toggle the modal this.isVisible ? null : this.toggle(); } } // Toggles the modal toggle() { let backdrop = document.querySelector(".modal-backdrop"); // Set the flag this._tranisitioningFl = true; // See if this modal is visible if (this.isVisible) { // Hide the modal this.el.classList.remove("show"); // Wait for the animation to complete setTimeout(() => { // Hide the modal this.el.style.display = ""; // Remove the backdrop backdrop ? document.body.removeChild(backdrop) : null; backdrop = null; // Set the flag this._tranisitioningFl = false; // Call the event this.props.onClose ? this.props.onClose(this.el) : null; }, 250); } else { // Start the animation this.el.classList.add("modal-open") this.el.style.display = "block"; // Create the backdrop if we are showing it let showBackdrop = this._options && typeof (this._options.backdrop) === "boolean" ? this._options.backdrop : true; if (showBackdrop && backdrop == null) { backdrop = document.createElement("div"); backdrop.classList.add("modal-backdrop"); backdrop.classList.add("fade"); backdrop.classList.add("show"); document.body.appendChild(backdrop); } // Set the focus (this.el as HTMLElement).focus(); // Wait for the animation to complete setTimeout(() => { // Show the modal this.el.classList.remove("modal-open"); this.el.classList.add("show"); // Set the flag this._tranisitioningFl = false; }, 250); } } } export const Modal = (props: IModalProps, template?: string): IModal => { return new _Modal(props, template); }