/*! * sweetalert2 v11.7.9 * Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Sweetalert2 = factory()); })(this, (function () { 'use strict'; const RESTORE_FOCUS_TIMEOUT = 100; /** @type {GlobalState} */ const globalState = {}; const focusPreviousActiveElement = () => { if (globalState.previousActiveElement instanceof HTMLElement) { globalState.previousActiveElement.focus(); globalState.previousActiveElement = null; } else if (document.body) { document.body.focus(); } }; /** * Restore previous active (focused) element * * @param {boolean} returnFocus * @returns {Promise} */ const restoreActiveElement = returnFocus => { return new Promise(resolve => { if (!returnFocus) { return resolve(); } const x = window.scrollX; const y = window.scrollY; globalState.restoreFocusTimeout = setTimeout(() => { focusPreviousActiveElement(); resolve(); }, RESTORE_FOCUS_TIMEOUT); // issues/900 window.scrollTo(x, y); }); }; /** * This module contains `WeakMap`s for each effectively-"private property" that a `Swal` has. * For example, to set the private property "foo" of `this` to "bar", you can `privateProps.foo.set(this, 'bar')` * This is the approach that Babel will probably take to implement private methods/fields * https://github.com/tc39/proposal-private-methods * https://github.com/babel/babel/pull/7555 * Once we have the changes from that PR in Babel, and our core class fits reasonable in *one module* * then we can use that language feature. */ var privateProps = { promise: new WeakMap(), innerParams: new WeakMap(), domCache: new WeakMap() }; const swalPrefix = 'swal2-'; /** * @typedef * { | 'container' * | 'shown' * | 'height-auto' * | 'iosfix' * | 'popup' * | 'modal' * | 'no-backdrop' * | 'no-transition' * | 'toast' * | 'toast-shown' * | 'show' * | 'hide' * | 'close' * | 'title' * | 'html-container' * | 'actions' * | 'confirm' * | 'deny' * | 'cancel' * | 'default-outline' * | 'footer' * | 'icon' * | 'icon-content' * | 'image' * | 'input' * | 'file' * | 'range' * | 'select' * | 'radio' * | 'checkbox' * | 'label' * | 'textarea' * | 'inputerror' * | 'input-label' * | 'validation-message' * | 'progress-steps' * | 'active-progress-step' * | 'progress-step' * | 'progress-step-line' * | 'loader' * | 'loading' * | 'styled' * | 'top' * | 'top-start' * | 'top-end' * | 'top-left' * | 'top-right' * | 'center' * | 'center-start' * | 'center-end' * | 'center-left' * | 'center-right' * | 'bottom' * | 'bottom-start' * | 'bottom-end' * | 'bottom-left' * | 'bottom-right' * | 'grow-row' * | 'grow-column' * | 'grow-fullscreen' * | 'rtl' * | 'timer-progress-bar' * | 'timer-progress-bar-container' * | 'scrollbar-measure' * | 'icon-success' * | 'icon-warning' * | 'icon-info' * | 'icon-question' * | 'icon-error' * } SwalClass * @typedef {Record} SwalClasses */ /** * @typedef {'success' | 'warning' | 'info' | 'question' | 'error'} SwalIcon * @typedef {Record} SwalIcons */ /** @type {SwalClass[]} */ const classNames = ['container', 'shown', 'height-auto', 'iosfix', 'popup', 'modal', 'no-backdrop', 'no-transition', 'toast', 'toast-shown', 'show', 'hide', 'close', 'title', 'html-container', 'actions', 'confirm', 'deny', 'cancel', 'default-outline', 'footer', 'icon', 'icon-content', 'image', 'input', 'file', 'range', 'select', 'radio', 'checkbox', 'label', 'textarea', 'inputerror', 'input-label', 'validation-message', 'progress-steps', 'active-progress-step', 'progress-step', 'progress-step-line', 'loader', 'loading', 'styled', 'top', 'top-start', 'top-end', 'top-left', 'top-right', 'center', 'center-start', 'center-end', 'center-left', 'center-right', 'bottom', 'bottom-start', 'bottom-end', 'bottom-left', 'bottom-right', 'grow-row', 'grow-column', 'grow-fullscreen', 'rtl', 'timer-progress-bar', 'timer-progress-bar-container', 'scrollbar-measure', 'icon-success', 'icon-warning', 'icon-info', 'icon-question', 'icon-error']; const swalClasses = classNames.reduce((acc, className) => { acc[className] = swalPrefix + className; return acc; }, /** @type {SwalClasses} */{}); /** @type {SwalIcon[]} */ const icons = ['success', 'warning', 'info', 'question', 'error']; const iconTypes = icons.reduce((acc, icon) => { acc[icon] = swalPrefix + icon; return acc; }, /** @type {SwalIcons} */{}); const consolePrefix = 'SweetAlert2:'; /** * Capitalize the first letter of a string * * @param {string} str * @returns {string} */ const capitalizeFirstLetter = str => str.charAt(0).toUpperCase() + str.slice(1); /** * Standardize console warnings * * @param {string | string[]} message */ const warn = message => { console.warn(`${consolePrefix} ${typeof message === 'object' ? message.join(' ') : message}`); }; /** * Standardize console errors * * @param {string} message */ const error = message => { console.error(`${consolePrefix} ${message}`); }; /** * Private global state for `warnOnce` * * @type {string[]} * @private */ const previousWarnOnceMessages = []; /** * Show a console warning, but only if it hasn't already been shown * * @param {string} message */ const warnOnce = message => { if (!previousWarnOnceMessages.includes(message)) { previousWarnOnceMessages.push(message); warn(message); } }; /** * Show a one-time console warning about deprecated params/methods * * @param {string} deprecatedParam * @param {string} useInstead */ const warnAboutDeprecation = (deprecatedParam, useInstead) => { warnOnce(`"${deprecatedParam}" is deprecated and will be removed in the next major release. Please use "${useInstead}" instead.`); }; /** * If `arg` is a function, call it (with no arguments or context) and return the result. * Otherwise, just pass the value through * * @param {Function | any} arg * @returns {any} */ const callIfFunction = arg => typeof arg === 'function' ? arg() : arg; /** * @param {any} arg * @returns {boolean} */ const hasToPromiseFn = arg => arg && typeof arg.toPromise === 'function'; /** * @param {any} arg * @returns {Promise} */ const asPromise = arg => hasToPromiseFn(arg) ? arg.toPromise() : Promise.resolve(arg); /** * @param {any} arg * @returns {boolean} */ const isPromise = arg => arg && Promise.resolve(arg) === arg; /** * Gets the popup container which contains the backdrop and the popup itself. * * @returns {HTMLElement | null} */ const getContainer = () => document.body.querySelector(`.${swalClasses.container}`); /** * @param {string} selectorString * @returns {HTMLElement | null} */ const elementBySelector = selectorString => { const container = getContainer(); return container ? container.querySelector(selectorString) : null; }; /** * @param {string} className * @returns {HTMLElement | null} */ const elementByClass = className => { return elementBySelector(`.${className}`); }; /** * @returns {HTMLElement | null} */ const getPopup = () => elementByClass(swalClasses.popup); /** * @returns {HTMLElement | null} */ const getIcon = () => elementByClass(swalClasses.icon); /** * @returns {HTMLElement | null} */ const getIconContent = () => elementByClass(swalClasses['icon-content']); /** * @returns {HTMLElement | null} */ const getTitle = () => elementByClass(swalClasses.title); /** * @returns {HTMLElement | null} */ const getHtmlContainer = () => elementByClass(swalClasses['html-container']); /** * @returns {HTMLElement | null} */ const getImage = () => elementByClass(swalClasses.image); /** * @returns {HTMLElement | null} */ const getProgressSteps = () => elementByClass(swalClasses['progress-steps']); /** * @returns {HTMLElement | null} */ const getValidationMessage = () => elementByClass(swalClasses['validation-message']); /** * @returns {HTMLButtonElement | null} */ const getConfirmButton = () => /** @type {HTMLButtonElement} */elementBySelector(`.${swalClasses.actions} .${swalClasses.confirm}`); /** * @returns {HTMLButtonElement | null} */ const getCancelButton = () => /** @type {HTMLButtonElement} */elementBySelector(`.${swalClasses.actions} .${swalClasses.cancel}`); /** * @returns {HTMLButtonElement | null} */ const getDenyButton = () => /** @type {HTMLButtonElement} */elementBySelector(`.${swalClasses.actions} .${swalClasses.deny}`); /** * @returns {HTMLElement | null} */ const getInputLabel = () => elementByClass(swalClasses['input-label']); /** * @returns {HTMLElement | null} */ const getLoader = () => elementBySelector(`.${swalClasses.loader}`); /** * @returns {HTMLElement | null} */ const getActions = () => elementByClass(swalClasses.actions); /** * @returns {HTMLElement | null} */ const getFooter = () => elementByClass(swalClasses.footer); /** * @returns {HTMLElement | null} */ const getTimerProgressBar = () => elementByClass(swalClasses['timer-progress-bar']); /** * @returns {HTMLElement | null} */ const getCloseButton = () => elementByClass(swalClasses.close); // https://github.com/jkup/focusable/blob/master/index.js const focusable = ` a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable], audio[controls], video[controls], summary `; /** * @returns {HTMLElement[]} */ const getFocusableElements = () => { /** @type {NodeListOf} */ const focusableElementsWithTabindex = getPopup().querySelectorAll('[tabindex]:not([tabindex="-1"]):not([tabindex="0"])'); const focusableElementsWithTabindexSorted = Array.from(focusableElementsWithTabindex) // sort according to tabindex .sort((a, b) => { const tabindexA = parseInt(a.getAttribute('tabindex')); const tabindexB = parseInt(b.getAttribute('tabindex')); if (tabindexA > tabindexB) { return 1; } else if (tabindexA < tabindexB) { return -1; } return 0; }); /** @type {NodeListOf} */ const otherFocusableElements = getPopup().querySelectorAll(focusable); const otherFocusableElementsFiltered = Array.from(otherFocusableElements).filter(el => el.getAttribute('tabindex') !== '-1'); return [...new Set(focusableElementsWithTabindexSorted.concat(otherFocusableElementsFiltered))].filter(el => isVisible$1(el)); }; /** * @returns {boolean} */ const isModal = () => { return hasClass(document.body, swalClasses.shown) && !hasClass(document.body, swalClasses['toast-shown']) && !hasClass(document.body, swalClasses['no-backdrop']); }; /** * @returns {boolean} */ const isToast = () => { return getPopup() && hasClass(getPopup(), swalClasses.toast); }; /** * @returns {boolean} */ const isLoading = () => { return getPopup().hasAttribute('data-loading'); }; /** * Securely set innerHTML of an element * https://github.com/sweetalert2/sweetalert2/issues/1926 * * @param {HTMLElement} elem * @param {string} html */ const setInnerHtml = (elem, html) => { elem.textContent = ''; if (html) { const parser = new DOMParser(); const parsed = parser.parseFromString(html, `text/html`); Array.from(parsed.querySelector('head').childNodes).forEach(child => { elem.appendChild(child); }); Array.from(parsed.querySelector('body').childNodes).forEach(child => { if (child instanceof HTMLVideoElement || child instanceof HTMLAudioElement) { elem.appendChild(child.cloneNode(true)); // https://github.com/sweetalert2/sweetalert2/issues/2507 } else { elem.appendChild(child); } }); } }; /** * @param {HTMLElement} elem * @param {string} className * @returns {boolean} */ const hasClass = (elem, className) => { if (!className) { return false; } const classList = className.split(/\s+/); for (let i = 0; i < classList.length; i++) { if (!elem.classList.contains(classList[i])) { return false; } } return true; }; /** * @param {HTMLElement} elem * @param {SweetAlertOptions} params */ const removeCustomClasses = (elem, params) => { Array.from(elem.classList).forEach(className => { if (!Object.values(swalClasses).includes(className) && !Object.values(iconTypes).includes(className) && !Object.values(params.showClass).includes(className)) { elem.classList.remove(className); } }); }; /** * @param {HTMLElement} elem * @param {SweetAlertOptions} params * @param {string} className */ const applyCustomClass = (elem, params, className) => { removeCustomClasses(elem, params); if (params.customClass && params.customClass[className]) { if (typeof params.customClass[className] !== 'string' && !params.customClass[className].forEach) { warn(`Invalid type of customClass.${className}! Expected string or iterable object, got "${typeof params.customClass[className]}"`); return; } addClass(elem, params.customClass[className]); } }; /** * @param {HTMLElement} popup * @param {import('./renderers/renderInput').InputClass} inputClass * @returns {HTMLInputElement | null} */ const getInput$1 = (popup, inputClass) => { if (!inputClass) { return null; } switch (inputClass) { case 'select': case 'textarea': case 'file': return popup.querySelector(`.${swalClasses.popup} > .${swalClasses[inputClass]}`); case 'checkbox': return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.checkbox} input`); case 'radio': return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.radio} input:checked`) || popup.querySelector(`.${swalClasses.popup} > .${swalClasses.radio} input:first-child`); case 'range': return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.range} input`); default: return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.input}`); } }; /** * @param {HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement} input */ const focusInput = input => { input.focus(); // place cursor at end of text in text input if (input.type !== 'file') { // http://stackoverflow.com/a/2345915 const val = input.value; input.value = ''; input.value = val; } }; /** * @param {HTMLElement | HTMLElement[] | null} target * @param {string | string[] | readonly string[]} classList * @param {boolean} condition */ const toggleClass = (target, classList, condition) => { if (!target || !classList) { return; } if (typeof classList === 'string') { classList = classList.split(/\s+/).filter(Boolean); } classList.forEach(className => { if (Array.isArray(target)) { target.forEach(elem => { condition ? elem.classList.add(className) : elem.classList.remove(className); }); } else { condition ? target.classList.add(className) : target.classList.remove(className); } }); }; /** * @param {HTMLElement | HTMLElement[] | null} target * @param {string | string[] | readonly string[]} classList */ const addClass = (target, classList) => { toggleClass(target, classList, true); }; /** * @param {HTMLElement | HTMLElement[] | null} target * @param {string | string[] | readonly string[]} classList */ const removeClass = (target, classList) => { toggleClass(target, classList, false); }; /** * Get direct child of an element by class name * * @param {HTMLElement} elem * @param {string} className * @returns {HTMLElement | undefined} */ const getDirectChildByClass = (elem, className) => { const children = Array.from(elem.children); for (let i = 0; i < children.length; i++) { const child = children[i]; if (child instanceof HTMLElement && hasClass(child, className)) { return child; } } }; /** * @param {HTMLElement} elem * @param {string} property * @param {*} value */ const applyNumericalStyle = (elem, property, value) => { if (value === `${parseInt(value)}`) { value = parseInt(value); } if (value || parseInt(value) === 0) { elem.style[property] = typeof value === 'number' ? `${value}px` : value; } else { elem.style.removeProperty(property); } }; /** * @param {HTMLElement} elem * @param {string} display */ const show = function (elem) { let display = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'flex'; elem.style.display = display; }; /** * @param {HTMLElement} elem */ const hide = elem => { elem.style.display = 'none'; }; /** * @param {HTMLElement} parent * @param {string} selector * @param {string} property * @param {string} value */ const setStyle = (parent, selector, property, value) => { /** @type {HTMLElement} */ const el = parent.querySelector(selector); if (el) { el.style[property] = value; } }; /** * @param {HTMLElement} elem * @param {any} condition * @param {string} display */ const toggle = function (elem, condition) { let display = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'flex'; condition ? show(elem, display) : hide(elem); }; /** * borrowed from jquery $(elem).is(':visible') implementation * * @param {HTMLElement} elem * @returns {boolean} */ const isVisible$1 = elem => !!(elem && (elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length)); /** * @returns {boolean} */ const allButtonsAreHidden = () => !isVisible$1(getConfirmButton()) && !isVisible$1(getDenyButton()) && !isVisible$1(getCancelButton()); /** * @param {HTMLElement} elem * @returns {boolean} */ const isScrollable = elem => !!(elem.scrollHeight > elem.clientHeight); /** * borrowed from https://stackoverflow.com/a/46352119 * * @param {HTMLElement} elem * @returns {boolean} */ const hasCssAnimation = elem => { const style = window.getComputedStyle(elem); const animDuration = parseFloat(style.getPropertyValue('animation-duration') || '0'); const transDuration = parseFloat(style.getPropertyValue('transition-duration') || '0'); return animDuration > 0 || transDuration > 0; }; /** * @param {number} timer * @param {boolean} reset */ const animateTimerProgressBar = function (timer) { let reset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; const timerProgressBar = getTimerProgressBar(); if (isVisible$1(timerProgressBar)) { if (reset) { timerProgressBar.style.transition = 'none'; timerProgressBar.style.width = '100%'; } setTimeout(() => { timerProgressBar.style.transition = `width ${timer / 1000}s linear`; timerProgressBar.style.width = '0%'; }, 10); } }; const stopTimerProgressBar = () => { const timerProgressBar = getTimerProgressBar(); const timerProgressBarWidth = parseInt(window.getComputedStyle(timerProgressBar).width); timerProgressBar.style.removeProperty('transition'); timerProgressBar.style.width = '100%'; const timerProgressBarFullWidth = parseInt(window.getComputedStyle(timerProgressBar).width); const timerProgressBarPercent = timerProgressBarWidth / timerProgressBarFullWidth * 100; timerProgressBar.style.width = `${timerProgressBarPercent}%`; }; /** * Detect Node env * * @returns {boolean} */ const isNodeEnv = () => typeof window === 'undefined' || typeof document === 'undefined'; const sweetHTML = `

    `.replace(/(^|\n)\s*/g, ''); /** * @returns {boolean} */ const resetOldContainer = () => { const oldContainer = getContainer(); if (!oldContainer) { return false; } oldContainer.remove(); removeClass([document.documentElement, document.body], [swalClasses['no-backdrop'], swalClasses['toast-shown'], swalClasses['has-column']]); return true; }; const resetValidationMessage$1 = () => { globalState.currentInstance.resetValidationMessage(); }; const addInputChangeListeners = () => { const popup = getPopup(); const input = getDirectChildByClass(popup, swalClasses.input); const file = getDirectChildByClass(popup, swalClasses.file); /** @type {HTMLInputElement} */ const range = popup.querySelector(`.${swalClasses.range} input`); /** @type {HTMLOutputElement} */ const rangeOutput = popup.querySelector(`.${swalClasses.range} output`); const select = getDirectChildByClass(popup, swalClasses.select); /** @type {HTMLInputElement} */ const checkbox = popup.querySelector(`.${swalClasses.checkbox} input`); const textarea = getDirectChildByClass(popup, swalClasses.textarea); input.oninput = resetValidationMessage$1; file.onchange = resetValidationMessage$1; select.onchange = resetValidationMessage$1; checkbox.onchange = resetValidationMessage$1; textarea.oninput = resetValidationMessage$1; range.oninput = () => { resetValidationMessage$1(); rangeOutput.value = range.value; }; range.onchange = () => { resetValidationMessage$1(); rangeOutput.value = range.value; }; }; /** * @param {string | HTMLElement} target * @returns {HTMLElement} */ const getTarget = target => typeof target === 'string' ? document.querySelector(target) : target; /** * @param {SweetAlertOptions} params */ const setupAccessibility = params => { const popup = getPopup(); popup.setAttribute('role', params.toast ? 'alert' : 'dialog'); popup.setAttribute('aria-live', params.toast ? 'polite' : 'assertive'); if (!params.toast) { popup.setAttribute('aria-modal', 'true'); } }; /** * @param {HTMLElement} targetElement */ const setupRTL = targetElement => { if (window.getComputedStyle(targetElement).direction === 'rtl') { addClass(getContainer(), swalClasses.rtl); } }; /** * Add modal + backdrop + no-war message for Russians to DOM * * @param {SweetAlertOptions} params */ const init = params => { // Clean up the old popup container if it exists const oldContainerExisted = resetOldContainer(); /* istanbul ignore if */ if (isNodeEnv()) { error('SweetAlert2 requires document to initialize'); return; } const container = document.createElement('div'); container.className = swalClasses.container; if (oldContainerExisted) { addClass(container, swalClasses['no-transition']); } setInnerHtml(container, sweetHTML); const targetElement = getTarget(params.target); targetElement.appendChild(container); setupAccessibility(params); setupRTL(targetElement); addInputChangeListeners(); }; /** * @param {HTMLElement | object | string} param * @param {HTMLElement} target */ const parseHtmlToContainer = (param, target) => { // DOM element if (param instanceof HTMLElement) { target.appendChild(param); } // Object else if (typeof param === 'object') { handleObject(param, target); } // Plain string else if (param) { setInnerHtml(target, param); } }; /** * @param {object} param * @param {HTMLElement} target */ const handleObject = (param, target) => { // JQuery element(s) if (param.jquery) { handleJqueryElem(target, param); } // For other objects use their string representation else { setInnerHtml(target, param.toString()); } }; /** * @param {HTMLElement} target * @param {HTMLElement} elem */ const handleJqueryElem = (target, elem) => { target.textContent = ''; if (0 in elem) { for (let i = 0; (i in elem); i++) { target.appendChild(elem[i].cloneNode(true)); } } else { target.appendChild(elem.cloneNode(true)); } }; /** * @returns {'webkitAnimationEnd' | 'animationend' | false} */ const animationEndEvent = (() => { // Prevent run in Node env /* istanbul ignore if */ if (isNodeEnv()) { return false; } const testEl = document.createElement('div'); const transEndEventNames = { WebkitAnimation: 'webkitAnimationEnd', // Chrome, Safari and Opera animation: 'animationend' // Standard syntax }; for (const i in transEndEventNames) { if (Object.prototype.hasOwnProperty.call(transEndEventNames, i) && typeof testEl.style[i] !== 'undefined') { return transEndEventNames[i]; } } return false; })(); /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderActions = (instance, params) => { const actions = getActions(); const loader = getLoader(); // Actions (buttons) wrapper if (!params.showConfirmButton && !params.showDenyButton && !params.showCancelButton) { hide(actions); } else { show(actions); } // Custom class applyCustomClass(actions, params, 'actions'); // Render all the buttons renderButtons(actions, loader, params); // Loader setInnerHtml(loader, params.loaderHtml); applyCustomClass(loader, params, 'loader'); }; /** * @param {HTMLElement} actions * @param {HTMLElement} loader * @param {SweetAlertOptions} params */ function renderButtons(actions, loader, params) { const confirmButton = getConfirmButton(); const denyButton = getDenyButton(); const cancelButton = getCancelButton(); // Render buttons renderButton(confirmButton, 'confirm', params); renderButton(denyButton, 'deny', params); renderButton(cancelButton, 'cancel', params); handleButtonsStyling(confirmButton, denyButton, cancelButton, params); if (params.reverseButtons) { if (params.toast) { actions.insertBefore(cancelButton, confirmButton); actions.insertBefore(denyButton, confirmButton); } else { actions.insertBefore(cancelButton, loader); actions.insertBefore(denyButton, loader); actions.insertBefore(confirmButton, loader); } } } /** * @param {HTMLElement} confirmButton * @param {HTMLElement} denyButton * @param {HTMLElement} cancelButton * @param {SweetAlertOptions} params */ function handleButtonsStyling(confirmButton, denyButton, cancelButton, params) { if (!params.buttonsStyling) { removeClass([confirmButton, denyButton, cancelButton], swalClasses.styled); return; } addClass([confirmButton, denyButton, cancelButton], swalClasses.styled); // Buttons background colors if (params.confirmButtonColor) { confirmButton.style.backgroundColor = params.confirmButtonColor; addClass(confirmButton, swalClasses['default-outline']); } if (params.denyButtonColor) { denyButton.style.backgroundColor = params.denyButtonColor; addClass(denyButton, swalClasses['default-outline']); } if (params.cancelButtonColor) { cancelButton.style.backgroundColor = params.cancelButtonColor; addClass(cancelButton, swalClasses['default-outline']); } } /** * @param {HTMLElement} button * @param {'confirm' | 'deny' | 'cancel'} buttonType * @param {SweetAlertOptions} params */ function renderButton(button, buttonType, params) { toggle(button, params[`show${capitalizeFirstLetter(buttonType)}Button`], 'inline-block'); setInnerHtml(button, params[`${buttonType}ButtonText`]); // Set caption text button.setAttribute('aria-label', params[`${buttonType}ButtonAriaLabel`]); // ARIA label // Add buttons custom classes button.className = swalClasses[buttonType]; applyCustomClass(button, params, `${buttonType}Button`); addClass(button, params[`${buttonType}ButtonClass`]); } /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderCloseButton = (instance, params) => { const closeButton = getCloseButton(); setInnerHtml(closeButton, params.closeButtonHtml); // Custom class applyCustomClass(closeButton, params, 'closeButton'); toggle(closeButton, params.showCloseButton); closeButton.setAttribute('aria-label', params.closeButtonAriaLabel); }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderContainer = (instance, params) => { const container = getContainer(); if (!container) { return; } handleBackdropParam(container, params.backdrop); handlePositionParam(container, params.position); handleGrowParam(container, params.grow); // Custom class applyCustomClass(container, params, 'container'); }; /** * @param {HTMLElement} container * @param {SweetAlertOptions['backdrop']} backdrop */ function handleBackdropParam(container, backdrop) { if (typeof backdrop === 'string') { container.style.background = backdrop; } else if (!backdrop) { addClass([document.documentElement, document.body], swalClasses['no-backdrop']); } } /** * @param {HTMLElement} container * @param {SweetAlertOptions['position']} position */ function handlePositionParam(container, position) { if (position in swalClasses) { addClass(container, swalClasses[position]); } else { warn('The "position" parameter is not valid, defaulting to "center"'); addClass(container, swalClasses.center); } } /** * @param {HTMLElement} container * @param {SweetAlertOptions['grow']} grow */ function handleGrowParam(container, grow) { if (grow && typeof grow === 'string') { const growClass = `grow-${grow}`; if (growClass in swalClasses) { addClass(container, swalClasses[growClass]); } } } /// /** @type {InputClass[]} */ const inputClasses = ['input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea']; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderInput = (instance, params) => { const popup = getPopup(); const innerParams = privateProps.innerParams.get(instance); const rerender = !innerParams || params.input !== innerParams.input; inputClasses.forEach(inputClass => { const inputContainer = getDirectChildByClass(popup, swalClasses[inputClass]); // set attributes setAttributes(inputClass, params.inputAttributes); // set class inputContainer.className = swalClasses[inputClass]; if (rerender) { hide(inputContainer); } }); if (params.input) { if (rerender) { showInput(params); } // set custom class setCustomClass(params); } }; /** * @param {SweetAlertOptions} params */ const showInput = params => { if (!renderInputType[params.input]) { error(`Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "${params.input}"`); return; } const inputContainer = getInputContainer(params.input); const input = renderInputType[params.input](inputContainer, params); show(inputContainer); // input autofocus if (params.inputAutoFocus) { setTimeout(() => { focusInput(input); }); } }; /** * @param {HTMLInputElement} input */ const removeAttributes = input => { for (let i = 0; i < input.attributes.length; i++) { const attrName = input.attributes[i].name; if (!['type', 'value', 'style'].includes(attrName)) { input.removeAttribute(attrName); } } }; /** * @param {InputClass} inputClass * @param {SweetAlertOptions['inputAttributes']} inputAttributes */ const setAttributes = (inputClass, inputAttributes) => { const input = getInput$1(getPopup(), inputClass); if (!input) { return; } removeAttributes(input); for (const attr in inputAttributes) { input.setAttribute(attr, inputAttributes[attr]); } }; /** * @param {SweetAlertOptions} params */ const setCustomClass = params => { const inputContainer = getInputContainer(params.input); if (typeof params.customClass === 'object') { addClass(inputContainer, params.customClass.input); } }; /** * @param {HTMLInputElement | HTMLTextAreaElement} input * @param {SweetAlertOptions} params */ const setInputPlaceholder = (input, params) => { if (!input.placeholder || params.inputPlaceholder) { input.placeholder = params.inputPlaceholder; } }; /** * @param {Input} input * @param {Input} prependTo * @param {SweetAlertOptions} params */ const setInputLabel = (input, prependTo, params) => { if (params.inputLabel) { input.id = swalClasses.input; const label = document.createElement('label'); const labelClass = swalClasses['input-label']; label.setAttribute('for', input.id); label.className = labelClass; if (typeof params.customClass === 'object') { addClass(label, params.customClass.inputLabel); } label.innerText = params.inputLabel; prependTo.insertAdjacentElement('beforebegin', label); } }; /** * @param {SweetAlertOptions['input']} inputType * @returns {HTMLElement} */ const getInputContainer = inputType => { return getDirectChildByClass(getPopup(), swalClasses[inputType] || swalClasses.input); }; /** * @param {HTMLInputElement | HTMLOutputElement | HTMLTextAreaElement} input * @param {SweetAlertOptions['inputValue']} inputValue */ const checkAndSetInputValue = (input, inputValue) => { if (['string', 'number'].includes(typeof inputValue)) { input.value = `${inputValue}`; } else if (!isPromise(inputValue)) { warn(`Unexpected type of inputValue! Expected "string", "number" or "Promise", got "${typeof inputValue}"`); } }; /** @type {Record Input>} */ const renderInputType = {}; /** * @param {HTMLInputElement} input * @param {SweetAlertOptions} params * @returns {HTMLInputElement} */ renderInputType.text = renderInputType.email = renderInputType.password = renderInputType.number = renderInputType.tel = renderInputType.url = (input, params) => { checkAndSetInputValue(input, params.inputValue); setInputLabel(input, input, params); setInputPlaceholder(input, params); input.type = params.input; return input; }; /** * @param {HTMLInputElement} input * @param {SweetAlertOptions} params * @returns {HTMLInputElement} */ renderInputType.file = (input, params) => { setInputLabel(input, input, params); setInputPlaceholder(input, params); return input; }; /** * @param {HTMLInputElement} range * @param {SweetAlertOptions} params * @returns {HTMLInputElement} */ renderInputType.range = (range, params) => { const rangeInput = range.querySelector('input'); const rangeOutput = range.querySelector('output'); checkAndSetInputValue(rangeInput, params.inputValue); rangeInput.type = params.input; checkAndSetInputValue(rangeOutput, params.inputValue); setInputLabel(rangeInput, range, params); return range; }; /** * @param {HTMLSelectElement} select * @param {SweetAlertOptions} params * @returns {HTMLSelectElement} */ renderInputType.select = (select, params) => { select.textContent = ''; if (params.inputPlaceholder) { const placeholder = document.createElement('option'); setInnerHtml(placeholder, params.inputPlaceholder); placeholder.value = ''; placeholder.disabled = true; placeholder.selected = true; select.appendChild(placeholder); } setInputLabel(select, select, params); return select; }; /** * @param {HTMLInputElement} radio * @returns {HTMLInputElement} */ renderInputType.radio = radio => { radio.textContent = ''; return radio; }; /** * @param {HTMLLabelElement} checkboxContainer * @param {SweetAlertOptions} params * @returns {HTMLInputElement} */ renderInputType.checkbox = (checkboxContainer, params) => { const checkbox = getInput$1(getPopup(), 'checkbox'); checkbox.value = '1'; checkbox.id = swalClasses.checkbox; checkbox.checked = Boolean(params.inputValue); const label = checkboxContainer.querySelector('span'); setInnerHtml(label, params.inputPlaceholder); return checkbox; }; /** * @param {HTMLTextAreaElement} textarea * @param {SweetAlertOptions} params * @returns {HTMLTextAreaElement} */ renderInputType.textarea = (textarea, params) => { checkAndSetInputValue(textarea, params.inputValue); setInputPlaceholder(textarea, params); setInputLabel(textarea, textarea, params); /** * @param {HTMLElement} el * @returns {number} */ const getMargin = el => parseInt(window.getComputedStyle(el).marginLeft) + parseInt(window.getComputedStyle(el).marginRight); // https://github.com/sweetalert2/sweetalert2/issues/2291 setTimeout(() => { // https://github.com/sweetalert2/sweetalert2/issues/1699 if ('MutationObserver' in window) { const initialPopupWidth = parseInt(window.getComputedStyle(getPopup()).width); const textareaResizeHandler = () => { const textareaWidth = textarea.offsetWidth + getMargin(textarea); if (textareaWidth > initialPopupWidth) { getPopup().style.width = `${textareaWidth}px`; } else { getPopup().style.width = null; } }; new MutationObserver(textareaResizeHandler).observe(textarea, { attributes: true, attributeFilter: ['style'] }); } }); return textarea; }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderContent = (instance, params) => { const htmlContainer = getHtmlContainer(); applyCustomClass(htmlContainer, params, 'htmlContainer'); // Content as HTML if (params.html) { parseHtmlToContainer(params.html, htmlContainer); show(htmlContainer, 'block'); } // Content as plain text else if (params.text) { htmlContainer.textContent = params.text; show(htmlContainer, 'block'); } // No content else { hide(htmlContainer); } renderInput(instance, params); }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderFooter = (instance, params) => { const footer = getFooter(); toggle(footer, params.footer); if (params.footer) { parseHtmlToContainer(params.footer, footer); } // Custom class applyCustomClass(footer, params, 'footer'); }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderIcon = (instance, params) => { const innerParams = privateProps.innerParams.get(instance); const icon = getIcon(); // if the given icon already rendered, apply the styling without re-rendering the icon if (innerParams && params.icon === innerParams.icon) { // Custom or default content setContent(icon, params); applyStyles(icon, params); return; } if (!params.icon && !params.iconHtml) { hide(icon); return; } if (params.icon && Object.keys(iconTypes).indexOf(params.icon) === -1) { error(`Unknown icon! Expected "success", "error", "warning", "info" or "question", got "${params.icon}"`); hide(icon); return; } show(icon); // Custom or default content setContent(icon, params); applyStyles(icon, params); // Animate icon addClass(icon, params.showClass.icon); }; /** * @param {HTMLElement} icon * @param {SweetAlertOptions} params */ const applyStyles = (icon, params) => { for (const iconType in iconTypes) { if (params.icon !== iconType) { removeClass(icon, iconTypes[iconType]); } } addClass(icon, iconTypes[params.icon]); // Icon color setColor(icon, params); // Success icon background color adjustSuccessIconBackgroundColor(); // Custom class applyCustomClass(icon, params, 'icon'); }; // Adjust success icon background color to match the popup background color const adjustSuccessIconBackgroundColor = () => { const popup = getPopup(); const popupBackgroundColor = window.getComputedStyle(popup).getPropertyValue('background-color'); /** @type {NodeListOf} */ const successIconParts = popup.querySelectorAll('[class^=swal2-success-circular-line], .swal2-success-fix'); for (let i = 0; i < successIconParts.length; i++) { successIconParts[i].style.backgroundColor = popupBackgroundColor; } }; const successIconHtml = `
    `; const errorIconHtml = ` `; /** * @param {HTMLElement} icon * @param {SweetAlertOptions} params */ const setContent = (icon, params) => { let oldContent = icon.innerHTML; let newContent; if (params.iconHtml) { newContent = iconContent(params.iconHtml); } else if (params.icon === 'success') { newContent = successIconHtml; oldContent = oldContent.replace(/ style=".*?"/g, ''); // undo adjustSuccessIconBackgroundColor() } else if (params.icon === 'error') { newContent = errorIconHtml; } else { const defaultIconHtml = { question: '?', warning: '!', info: 'i' }; newContent = iconContent(defaultIconHtml[params.icon]); } if (oldContent.trim() !== newContent.trim()) { setInnerHtml(icon, newContent); } }; /** * @param {HTMLElement} icon * @param {SweetAlertOptions} params */ const setColor = (icon, params) => { if (!params.iconColor) { return; } icon.style.color = params.iconColor; icon.style.borderColor = params.iconColor; for (const sel of ['.swal2-success-line-tip', '.swal2-success-line-long', '.swal2-x-mark-line-left', '.swal2-x-mark-line-right']) { setStyle(icon, sel, 'backgroundColor', params.iconColor); } setStyle(icon, '.swal2-success-ring', 'borderColor', params.iconColor); }; /** * @param {string} content * @returns {string} */ const iconContent = content => `
    ${content}
    `; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderImage = (instance, params) => { const image = getImage(); if (!image) { return; } if (!params.imageUrl) { hide(image); return; } show(image, ''); // Src, alt image.setAttribute('src', params.imageUrl); image.setAttribute('alt', params.imageAlt || ''); // Width, height applyNumericalStyle(image, 'width', params.imageWidth); applyNumericalStyle(image, 'height', params.imageHeight); // Class image.className = swalClasses.image; applyCustomClass(image, params, 'image'); }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderPopup = (instance, params) => { const container = getContainer(); const popup = getPopup(); // Width // https://github.com/sweetalert2/sweetalert2/issues/2170 if (params.toast) { applyNumericalStyle(container, 'width', params.width); popup.style.width = '100%'; popup.insertBefore(getLoader(), getIcon()); } else { applyNumericalStyle(popup, 'width', params.width); } // Padding applyNumericalStyle(popup, 'padding', params.padding); // Color if (params.color) { popup.style.color = params.color; } // Background if (params.background) { popup.style.background = params.background; } hide(getValidationMessage()); // Classes addClasses$1(popup, params); }; /** * @param {HTMLElement} popup * @param {SweetAlertOptions} params */ const addClasses$1 = (popup, params) => { // Default Class + showClass when updating Swal.update({}) popup.className = `${swalClasses.popup} ${isVisible$1(popup) ? params.showClass.popup : ''}`; if (params.toast) { addClass([document.documentElement, document.body], swalClasses['toast-shown']); addClass(popup, swalClasses.toast); } else { addClass(popup, swalClasses.modal); } // Custom class applyCustomClass(popup, params, 'popup'); if (typeof params.customClass === 'string') { addClass(popup, params.customClass); } // Icon class (#1842) if (params.icon) { addClass(popup, swalClasses[`icon-${params.icon}`]); } }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderProgressSteps = (instance, params) => { const progressStepsContainer = getProgressSteps(); if (!progressStepsContainer) { return; } const { progressSteps, currentProgressStep } = params; if (!progressSteps || progressSteps.length === 0 || currentProgressStep === undefined) { hide(progressStepsContainer); return; } show(progressStepsContainer); progressStepsContainer.textContent = ''; if (currentProgressStep >= progressSteps.length) { warn('Invalid currentProgressStep parameter, it should be less than progressSteps.length ' + '(currentProgressStep like JS arrays starts from 0)'); } progressSteps.forEach((step, index) => { const stepEl = createStepElement(step); progressStepsContainer.appendChild(stepEl); if (index === currentProgressStep) { addClass(stepEl, swalClasses['active-progress-step']); } if (index !== progressSteps.length - 1) { const lineEl = createLineElement(params); progressStepsContainer.appendChild(lineEl); } }); }; /** * @param {string} step * @returns {HTMLLIElement} */ const createStepElement = step => { const stepEl = document.createElement('li'); addClass(stepEl, swalClasses['progress-step']); setInnerHtml(stepEl, step); return stepEl; }; /** * @param {SweetAlertOptions} params * @returns {HTMLLIElement} */ const createLineElement = params => { const lineEl = document.createElement('li'); addClass(lineEl, swalClasses['progress-step-line']); if (params.progressStepsDistance) { applyNumericalStyle(lineEl, 'width', params.progressStepsDistance); } return lineEl; }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const renderTitle = (instance, params) => { const title = getTitle(); toggle(title, params.title || params.titleText, 'block'); if (params.title) { parseHtmlToContainer(params.title, title); } if (params.titleText) { title.innerText = params.titleText; } // Custom class applyCustomClass(title, params, 'title'); }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const render = (instance, params) => { renderPopup(instance, params); renderContainer(instance, params); renderProgressSteps(instance, params); renderIcon(instance, params); renderImage(instance, params); renderTitle(instance, params); renderCloseButton(instance, params); renderContent(instance, params); renderActions(instance, params); renderFooter(instance, params); if (typeof params.didRender === 'function') { params.didRender(getPopup()); } }; /* * Global function to determine if SweetAlert2 popup is shown */ const isVisible = () => { return isVisible$1(getPopup()); }; /* * Global function to click 'Confirm' button */ const clickConfirm = () => getConfirmButton() && getConfirmButton().click(); /* * Global function to click 'Deny' button */ const clickDeny = () => getDenyButton() && getDenyButton().click(); /* * Global function to click 'Cancel' button */ const clickCancel = () => getCancelButton() && getCancelButton().click(); /** @typedef {'cancel' | 'backdrop' | 'close' | 'esc' | 'timer'} DismissReason */ /** @type {Record} */ const DismissReason = Object.freeze({ cancel: 'cancel', backdrop: 'backdrop', close: 'close', esc: 'esc', timer: 'timer' }); /** * @param {GlobalState} globalState */ const removeKeydownHandler = globalState => { if (globalState.keydownTarget && globalState.keydownHandlerAdded) { globalState.keydownTarget.removeEventListener('keydown', globalState.keydownHandler, { capture: globalState.keydownListenerCapture }); globalState.keydownHandlerAdded = false; } }; /** * @param {SweetAlert} instance * @param {GlobalState} globalState * @param {SweetAlertOptions} innerParams * @param {*} dismissWith */ const addKeydownHandler = (instance, globalState, innerParams, dismissWith) => { removeKeydownHandler(globalState); if (!innerParams.toast) { globalState.keydownHandler = e => keydownHandler(instance, e, dismissWith); globalState.keydownTarget = innerParams.keydownListenerCapture ? window : getPopup(); globalState.keydownListenerCapture = innerParams.keydownListenerCapture; globalState.keydownTarget.addEventListener('keydown', globalState.keydownHandler, { capture: globalState.keydownListenerCapture }); globalState.keydownHandlerAdded = true; } }; /** * @param {number} index * @param {number} increment */ const setFocus = (index, increment) => { const focusableElements = getFocusableElements(); // search for visible elements and select the next possible match if (focusableElements.length) { index = index + increment; // rollover to first item if (index === focusableElements.length) { index = 0; // go to last item } else if (index === -1) { index = focusableElements.length - 1; } focusableElements[index].focus(); return; } // no visible focusable elements, focus the popup getPopup().focus(); }; const arrowKeysNextButton = ['ArrowRight', 'ArrowDown']; const arrowKeysPreviousButton = ['ArrowLeft', 'ArrowUp']; /** * @param {SweetAlert} instance * @param {KeyboardEvent} event * @param {Function} dismissWith */ const keydownHandler = (instance, event, dismissWith) => { const innerParams = privateProps.innerParams.get(instance); if (!innerParams) { return; // This instance has already been destroyed } // Ignore keydown during IME composition // https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event#ignoring_keydown_during_ime_composition // https://github.com/sweetalert2/sweetalert2/issues/720 // https://github.com/sweetalert2/sweetalert2/issues/2406 if (event.isComposing || event.keyCode === 229) { return; } if (innerParams.stopKeydownPropagation) { event.stopPropagation(); } // ENTER if (event.key === 'Enter') { handleEnter(instance, event, innerParams); } // TAB else if (event.key === 'Tab') { handleTab(event); } // ARROWS - switch focus between buttons else if ([...arrowKeysNextButton, ...arrowKeysPreviousButton].includes(event.key)) { handleArrows(event.key); } // ESC else if (event.key === 'Escape') { handleEsc(event, innerParams, dismissWith); } }; /** * @param {SweetAlert} instance * @param {KeyboardEvent} event * @param {SweetAlertOptions} innerParams */ const handleEnter = (instance, event, innerParams) => { // https://github.com/sweetalert2/sweetalert2/issues/2386 if (!callIfFunction(innerParams.allowEnterKey)) { return; } if (event.target && instance.getInput() && event.target instanceof HTMLElement && event.target.outerHTML === instance.getInput().outerHTML) { if (['textarea', 'file'].includes(innerParams.input)) { return; // do not submit } clickConfirm(); event.preventDefault(); } }; /** * @param {KeyboardEvent} event */ const handleTab = event => { const targetElement = event.target; const focusableElements = getFocusableElements(); let btnIndex = -1; for (let i = 0; i < focusableElements.length; i++) { if (targetElement === focusableElements[i]) { btnIndex = i; break; } } // Cycle to the next button if (!event.shiftKey) { setFocus(btnIndex, 1); } // Cycle to the prev button else { setFocus(btnIndex, -1); } event.stopPropagation(); event.preventDefault(); }; /** * @param {string} key */ const handleArrows = key => { const confirmButton = getConfirmButton(); const denyButton = getDenyButton(); const cancelButton = getCancelButton(); /** @type HTMLElement[] */ const buttons = [confirmButton, denyButton, cancelButton]; if (document.activeElement instanceof HTMLElement && !buttons.includes(document.activeElement)) { return; } const sibling = arrowKeysNextButton.includes(key) ? 'nextElementSibling' : 'previousElementSibling'; let buttonToFocus = document.activeElement; for (let i = 0; i < getActions().children.length; i++) { buttonToFocus = buttonToFocus[sibling]; if (!buttonToFocus) { return; } if (buttonToFocus instanceof HTMLButtonElement && isVisible$1(buttonToFocus)) { break; } } if (buttonToFocus instanceof HTMLButtonElement) { buttonToFocus.focus(); } }; /** * @param {KeyboardEvent} event * @param {SweetAlertOptions} innerParams * @param {Function} dismissWith */ const handleEsc = (event, innerParams, dismissWith) => { if (callIfFunction(innerParams.allowEscapeKey)) { event.preventDefault(); dismissWith(DismissReason.esc); } }; /** * This module contains `WeakMap`s for each effectively-"private property" that a `Swal` has. * For example, to set the private property "foo" of `this` to "bar", you can `privateProps.foo.set(this, 'bar')` * This is the approach that Babel will probably take to implement private methods/fields * https://github.com/tc39/proposal-private-methods * https://github.com/babel/babel/pull/7555 * Once we have the changes from that PR in Babel, and our core class fits reasonable in *one module* * then we can use that language feature. */ var privateMethods = { swalPromiseResolve: new WeakMap(), swalPromiseReject: new WeakMap() }; // From https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/ // Adding aria-hidden="true" to elements outside of the active modal dialog ensures that // elements not within the active modal dialog will not be surfaced if a user opens a screen // reader’s list of elements (headings, form controls, landmarks, etc.) in the document. const setAriaHidden = () => { const bodyChildren = Array.from(document.body.children); bodyChildren.forEach(el => { if (el === getContainer() || el.contains(getContainer())) { return; } if (el.hasAttribute('aria-hidden')) { el.setAttribute('data-previous-aria-hidden', el.getAttribute('aria-hidden')); } el.setAttribute('aria-hidden', 'true'); }); }; const unsetAriaHidden = () => { const bodyChildren = Array.from(document.body.children); bodyChildren.forEach(el => { if (el.hasAttribute('data-previous-aria-hidden')) { el.setAttribute('aria-hidden', el.getAttribute('data-previous-aria-hidden')); el.removeAttribute('data-previous-aria-hidden'); } else { el.removeAttribute('aria-hidden'); } }); }; /* istanbul ignore file */ // Fix iOS scrolling http://stackoverflow.com/q/39626302 const iOSfix = () => { const iOS = // @ts-ignore /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream || navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1; if (iOS && !hasClass(document.body, swalClasses.iosfix)) { const offset = document.body.scrollTop; document.body.style.top = `${offset * -1}px`; addClass(document.body, swalClasses.iosfix); lockBodyScroll(); addBottomPaddingForTallPopups(); } }; /** * https://github.com/sweetalert2/sweetalert2/issues/1948 */ const addBottomPaddingForTallPopups = () => { const ua = navigator.userAgent; const iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i); const webkit = !!ua.match(/WebKit/i); const iOSSafari = iOS && webkit && !ua.match(/CriOS/i); if (iOSSafari) { const bottomPanelHeight = 44; if (getPopup().scrollHeight > window.innerHeight - bottomPanelHeight) { getContainer().style.paddingBottom = `${bottomPanelHeight}px`; } } }; /** * https://github.com/sweetalert2/sweetalert2/issues/1246 */ const lockBodyScroll = () => { const container = getContainer(); let preventTouchMove; /** * @param {TouchEvent} event */ container.ontouchstart = event => { preventTouchMove = shouldPreventTouchMove(event); }; /** * @param {TouchEvent} event */ container.ontouchmove = event => { if (preventTouchMove) { event.preventDefault(); event.stopPropagation(); } }; }; /** * @param {TouchEvent} event * @returns {boolean} */ const shouldPreventTouchMove = event => { const target = event.target; const container = getContainer(); if (isStylus(event) || isZoom(event)) { return false; } if (target === container) { return true; } if (!isScrollable(container) && target instanceof HTMLElement && target.tagName !== 'INPUT' && // #1603 target.tagName !== 'TEXTAREA' && // #2266 !(isScrollable(getHtmlContainer()) && // #1944 getHtmlContainer().contains(target))) { return true; } return false; }; /** * https://github.com/sweetalert2/sweetalert2/issues/1786 * * @param {*} event * @returns {boolean} */ const isStylus = event => { return event.touches && event.touches.length && event.touches[0].touchType === 'stylus'; }; /** * https://github.com/sweetalert2/sweetalert2/issues/1891 * * @param {TouchEvent} event * @returns {boolean} */ const isZoom = event => { return event.touches && event.touches.length > 1; }; const undoIOSfix = () => { if (hasClass(document.body, swalClasses.iosfix)) { const offset = parseInt(document.body.style.top, 10); removeClass(document.body, swalClasses.iosfix); document.body.style.top = ''; document.body.scrollTop = offset * -1; } }; /** * Measure scrollbar width for padding body during modal show/hide * https://github.com/twbs/bootstrap/blob/master/js/src/modal.js * * @returns {number} */ const measureScrollbar = () => { const scrollDiv = document.createElement('div'); scrollDiv.className = swalClasses['scrollbar-measure']; document.body.appendChild(scrollDiv); const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth; document.body.removeChild(scrollDiv); return scrollbarWidth; }; /** * Remember state in cases where opening and handling a modal will fiddle with it. * @type {number | null} */ let previousBodyPadding = null; const fixScrollbar = () => { // for queues, do not do this more than once if (previousBodyPadding !== null) { return; } // if the body has overflow if (document.body.scrollHeight > window.innerHeight) { // add padding so the content doesn't shift after removal of scrollbar previousBodyPadding = parseInt(window.getComputedStyle(document.body).getPropertyValue('padding-right')); document.body.style.paddingRight = `${previousBodyPadding + measureScrollbar()}px`; } }; const undoScrollbar = () => { if (previousBodyPadding !== null) { document.body.style.paddingRight = `${previousBodyPadding}px`; previousBodyPadding = null; } }; /** * @param {SweetAlert} instance * @param {HTMLElement} container * @param {boolean} returnFocus * @param {Function} didClose */ function removePopupAndResetState(instance, container, returnFocus, didClose) { if (isToast()) { triggerDidCloseAndDispose(instance, didClose); } else { restoreActiveElement(returnFocus).then(() => triggerDidCloseAndDispose(instance, didClose)); removeKeydownHandler(globalState); } const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // workaround for #2088 // for some reason removing the container in Safari will scroll the document to bottom if (isSafari) { container.setAttribute('style', 'display:none !important'); container.removeAttribute('class'); container.innerHTML = ''; } else { container.remove(); } if (isModal()) { undoScrollbar(); undoIOSfix(); unsetAriaHidden(); } removeBodyClasses(); } /** * Remove SweetAlert2 classes from body */ function removeBodyClasses() { removeClass([document.documentElement, document.body], [swalClasses.shown, swalClasses['height-auto'], swalClasses['no-backdrop'], swalClasses['toast-shown']]); } /** * Instance method to close sweetAlert * * @param {any} resolveValue */ function close(resolveValue) { resolveValue = prepareResolveValue(resolveValue); const swalPromiseResolve = privateMethods.swalPromiseResolve.get(this); const didClose = triggerClosePopup(this); if (this.isAwaitingPromise) { // A swal awaiting for a promise (after a click on Confirm or Deny) cannot be dismissed anymore #2335 if (!resolveValue.isDismissed) { handleAwaitingPromise(this); swalPromiseResolve(resolveValue); } } else if (didClose) { // Resolve Swal promise swalPromiseResolve(resolveValue); } } const triggerClosePopup = instance => { const popup = getPopup(); if (!popup) { return false; } const innerParams = privateProps.innerParams.get(instance); if (!innerParams || hasClass(popup, innerParams.hideClass.popup)) { return false; } removeClass(popup, innerParams.showClass.popup); addClass(popup, innerParams.hideClass.popup); const backdrop = getContainer(); removeClass(backdrop, innerParams.showClass.backdrop); addClass(backdrop, innerParams.hideClass.backdrop); handlePopupAnimation(instance, popup, innerParams); return true; }; /** * @param {any} error */ function rejectPromise(error) { const rejectPromise = privateMethods.swalPromiseReject.get(this); handleAwaitingPromise(this); if (rejectPromise) { // Reject Swal promise rejectPromise(error); } } /** * @param {SweetAlert} instance */ const handleAwaitingPromise = instance => { if (instance.isAwaitingPromise) { delete instance.isAwaitingPromise; // The instance might have been previously partly destroyed, we must resume the destroy process in this case #2335 if (!privateProps.innerParams.get(instance)) { instance._destroy(); } } }; /** * @param {any} resolveValue * @returns {SweetAlertResult} */ const prepareResolveValue = resolveValue => { // When user calls Swal.close() if (typeof resolveValue === 'undefined') { return { isConfirmed: false, isDenied: false, isDismissed: true }; } return Object.assign({ isConfirmed: false, isDenied: false, isDismissed: false }, resolveValue); }; /** * @param {SweetAlert} instance * @param {HTMLElement} popup * @param {SweetAlertOptions} innerParams */ const handlePopupAnimation = (instance, popup, innerParams) => { const container = getContainer(); // If animation is supported, animate const animationIsSupported = animationEndEvent && hasCssAnimation(popup); if (typeof innerParams.willClose === 'function') { innerParams.willClose(popup); } if (animationIsSupported) { animatePopup(instance, popup, container, innerParams.returnFocus, innerParams.didClose); } else { // Otherwise, remove immediately removePopupAndResetState(instance, container, innerParams.returnFocus, innerParams.didClose); } }; /** * @param {SweetAlert} instance * @param {HTMLElement} popup * @param {HTMLElement} container * @param {boolean} returnFocus * @param {Function} didClose */ const animatePopup = (instance, popup, container, returnFocus, didClose) => { globalState.swalCloseEventFinishedCallback = removePopupAndResetState.bind(null, instance, container, returnFocus, didClose); popup.addEventListener(animationEndEvent, function (e) { if (e.target === popup) { globalState.swalCloseEventFinishedCallback(); delete globalState.swalCloseEventFinishedCallback; } }); }; /** * @param {SweetAlert} instance * @param {Function} didClose */ const triggerDidCloseAndDispose = (instance, didClose) => { setTimeout(() => { if (typeof didClose === 'function') { didClose.bind(instance.params)(); } // instance might have been destroyed already if (instance._destroy) { instance._destroy(); } }); }; /** * Shows loader (spinner), this is useful with AJAX requests. * By default the loader be shown instead of the "Confirm" button. * * @param {HTMLButtonElement} [buttonToReplace] */ const showLoading = buttonToReplace => { let popup = getPopup(); if (!popup) { new Swal(); // eslint-disable-line no-new } popup = getPopup(); const loader = getLoader(); if (isToast()) { hide(getIcon()); } else { replaceButton(popup, buttonToReplace); } show(loader); popup.setAttribute('data-loading', 'true'); popup.setAttribute('aria-busy', 'true'); popup.focus(); }; /** * @param {HTMLElement} popup * @param {HTMLButtonElement} [buttonToReplace] */ const replaceButton = (popup, buttonToReplace) => { const actions = getActions(); const loader = getLoader(); if (!buttonToReplace && isVisible$1(getConfirmButton())) { buttonToReplace = getConfirmButton(); } show(actions); if (buttonToReplace) { hide(buttonToReplace); loader.setAttribute('data-button-to-replace', buttonToReplace.className); } loader.parentNode.insertBefore(loader, buttonToReplace); addClass([popup, actions], swalClasses.loading); }; /** * @typedef { string | number | boolean } InputValue */ /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const handleInputOptionsAndValue = (instance, params) => { if (params.input === 'select' || params.input === 'radio') { handleInputOptions(instance, params); } else if (['text', 'email', 'number', 'tel', 'textarea'].includes(params.input) && (hasToPromiseFn(params.inputValue) || isPromise(params.inputValue))) { showLoading(getConfirmButton()); handleInputValue(instance, params); } }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} innerParams * @returns {string | number | File | FileList | null} */ const getInputValue = (instance, innerParams) => { const input = instance.getInput(); if (!input) { return null; } switch (innerParams.input) { case 'checkbox': return getCheckboxValue(input); case 'radio': return getRadioValue(input); case 'file': return getFileValue(input); default: return innerParams.inputAutoTrim ? input.value.trim() : input.value; } }; /** * @param {HTMLInputElement} input * @returns {number} */ const getCheckboxValue = input => input.checked ? 1 : 0; /** * @param {HTMLInputElement} input * @returns {string | null} */ const getRadioValue = input => input.checked ? input.value : null; /** * @param {HTMLInputElement} input * @returns {FileList | File | null} */ const getFileValue = input => input.files.length ? input.getAttribute('multiple') !== null ? input.files : input.files[0] : null; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const handleInputOptions = (instance, params) => { const popup = getPopup(); /** * @param {Record} inputOptions */ const processInputOptions = inputOptions => { populateInputOptions[params.input](popup, formatInputOptions(inputOptions), params); }; if (hasToPromiseFn(params.inputOptions) || isPromise(params.inputOptions)) { showLoading(getConfirmButton()); asPromise(params.inputOptions).then(inputOptions => { instance.hideLoading(); processInputOptions(inputOptions); }); } else if (typeof params.inputOptions === 'object') { processInputOptions(params.inputOptions); } else { error(`Unexpected type of inputOptions! Expected object, Map or Promise, got ${typeof params.inputOptions}`); } }; /** * @param {SweetAlert} instance * @param {SweetAlertOptions} params */ const handleInputValue = (instance, params) => { const input = instance.getInput(); hide(input); asPromise(params.inputValue).then(inputValue => { input.value = params.input === 'number' ? `${parseFloat(inputValue) || 0}` : `${inputValue}`; show(input); input.focus(); instance.hideLoading(); }).catch(err => { error(`Error in inputValue promise: ${err}`); input.value = ''; show(input); input.focus(); instance.hideLoading(); }); }; const populateInputOptions = { /** * @param {HTMLElement} popup * @param {Record} inputOptions * @param {SweetAlertOptions} params */ select: (popup, inputOptions, params) => { const select = getDirectChildByClass(popup, swalClasses.select); /** * @param {HTMLElement} parent * @param {string} optionLabel * @param {string} optionValue */ const renderOption = (parent, optionLabel, optionValue) => { const option = document.createElement('option'); option.value = optionValue; setInnerHtml(option, optionLabel); option.selected = isSelected(optionValue, params.inputValue); parent.appendChild(option); }; inputOptions.forEach(inputOption => { const optionValue = inputOption[0]; const optionLabel = inputOption[1]; // spec: // https://www.w3.org/TR/html401/interact/forms.html#h-17.6 // "...all OPTGROUP elements must be specified directly within a SELECT element (i.e., groups may not be nested)..." // check whether this is a if (Array.isArray(optionLabel)) { // if it is an array, then it is an const optgroup = document.createElement('optgroup'); optgroup.label = optionValue; optgroup.disabled = false; // not configurable for now select.appendChild(optgroup); optionLabel.forEach(o => renderOption(optgroup, o[1], o[0])); } else { // case of