UNPKG

sweetalert2

Version:

A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes, supported fork of sweetalert

376 lines (317 loc) 11.5 kB
import { default as sweetAlert } from '../sweetalert2.js' import { swalClasses, iconTypes } from './classes.js' import { uniqueArray, error, warnOnce } from './utils.js' // Remember state in cases where opening and handling a modal will fiddle with it. export const states = { previousActiveElement: null, previousBodyPadding: null } // Detect Node env export const isNodeEnv = () => typeof window === 'undefined' || typeof document === 'undefined' /* * Add modal + backdrop to DOM */ export const init = (params) => { // Clean up the old popup if it exists const c = getContainer() if (c) { c.parentNode.removeChild(c) removeClass( [document.documentElement, document.body], [ swalClasses['no-backdrop'], swalClasses['has-input'], swalClasses['toast-shown'] ] ) } if (isNodeEnv()) { error('SweetAlert2 requires document to initialize') return } const container = document.createElement('div') container.className = swalClasses.container container.innerHTML = sweetHTML let targetElement = typeof params.target === 'string' ? document.querySelector(params.target) : params.target targetElement.appendChild(container) const popup = getPopup() const content = getContent() const input = getChildByClass(content, swalClasses.input) const file = getChildByClass(content, swalClasses.file) const range = content.querySelector(`.${swalClasses.range} input`) const rangeOutput = content.querySelector(`.${swalClasses.range} output`) const select = getChildByClass(content, swalClasses.select) const checkbox = content.querySelector(`.${swalClasses.checkbox} input`) const textarea = getChildByClass(content, swalClasses.textarea) // a11y popup.setAttribute('role', params.toast ? 'alert' : 'dialog') popup.setAttribute('aria-live', params.toast ? 'polite' : 'assertive') if (!params.toast) { popup.setAttribute('aria-modal', 'true') } const resetValidationError = () => { sweetAlert.isVisible() && sweetAlert.resetValidationError() } input.oninput = resetValidationError file.onchange = resetValidationError select.onchange = resetValidationError checkbox.onchange = resetValidationError textarea.oninput = resetValidationError range.oninput = () => { resetValidationError() rangeOutput.value = range.value } range.onchange = () => { resetValidationError() range.nextSibling.value = range.value } return popup } /* * Manipulate DOM */ const sweetHTML = ` <div aria-labelledby="${swalClasses.title}" aria-describedby="${swalClasses.content}" class="${swalClasses.popup}" tabindex="-1"> <div class="${swalClasses.header}"> <ul class="${swalClasses.progresssteps}"></ul> <div class="${swalClasses.icon} ${iconTypes.error}"> <span class="swal2-x-mark"><span class="swal2-x-mark-line-left"></span><span class="swal2-x-mark-line-right"></span></span> </div> <div class="${swalClasses.icon} ${iconTypes.question}">?</div> <div class="${swalClasses.icon} ${iconTypes.warning}">!</div> <div class="${swalClasses.icon} ${iconTypes.info}">i</div> <div class="${swalClasses.icon} ${iconTypes.success}"> <div class="swal2-success-circular-line-left"></div> <span class="swal2-success-line-tip"></span> <span class="swal2-success-line-long"></span> <div class="swal2-success-ring"></div> <div class="swal2-success-fix"></div> <div class="swal2-success-circular-line-right"></div> </div> <img class="${swalClasses.image}" /> <h2 class="${swalClasses.title}" id="${swalClasses.title}"></h2> <button type="button" class="${swalClasses.close}">×</button> </div> <div class="${swalClasses.content}"> <div id="${swalClasses.content}"></div> <input class="${swalClasses.input}" /> <input type="file" class="${swalClasses.file}" /> <div class="${swalClasses.range}"> <input type="range" /> <output></output> </div> <select class="${swalClasses.select}"></select> <div class="${swalClasses.radio}"></div> <label for="${swalClasses.checkbox}" class="${swalClasses.checkbox}"> <input type="checkbox" /> </label> <textarea class="${swalClasses.textarea}"></textarea> <div class="${swalClasses.validationerror}" id="${swalClasses.validationerror}"></div> </div> <div class="${swalClasses.actions}"> <button type="button" class="${swalClasses.confirm}">OK</button> <button type="button" class="${swalClasses.cancel}">Cancel</button> </div> <div class="${swalClasses.footer}"> </div> </div> `.replace(/(^|\n)\s*/g, '') export const getContainer = () => document.body.querySelector('.' + swalClasses.container) export const getPopup = () => getContainer() ? getContainer().querySelector('.' + swalClasses.popup) : null export const getIcons = () => { const popup = getPopup() return popup.querySelectorAll('.' + swalClasses.icon) } export const elementByClass = (className) => getContainer() ? getContainer().querySelector('.' + className) : null export const getTitle = () => elementByClass(swalClasses.title) export const getContent = () => elementByClass(swalClasses.content) export const getImage = () => elementByClass(swalClasses.image) export const getProgressSteps = () => elementByClass(swalClasses.progresssteps) export const getValidationError = () => elementByClass(swalClasses.validationerror) export const getConfirmButton = () => elementByClass(swalClasses.confirm) export const getCancelButton = () => elementByClass(swalClasses.cancel) export const getButtonsWrapper = () => { warnOnce(`swal.getButtonsWrapper() is deprecated and will be removed in the next major release, use swal.getActions() instead`) return elementByClass(swalClasses.actions) } export const getActions = () => elementByClass(swalClasses.actions) export const getFooter = () => elementByClass(swalClasses.footer) export const getCloseButton = () => elementByClass(swalClasses.close) export const getFocusableElements = () => { const focusableElementsWithTabindex = Array.prototype.slice.call( getPopup().querySelectorAll('[tabindex]:not([tabindex="-1"]):not([tabindex="0"])') ) // sort according to tabindex .sort((a, b) => { a = parseInt(a.getAttribute('tabindex')) b = parseInt(b.getAttribute('tabindex')) if (a > b) { return 1 } else if (a < b) { return -1 } return 0 }) const otherFocusableElements = Array.prototype.slice.call( getPopup().querySelectorAll('button, input:not([type=hidden]), textarea, select, a, [tabindex="0"]') ) return uniqueArray(focusableElementsWithTabindex.concat(otherFocusableElements)) } export const parseHtmlToContainer = (param, target) => { if (!param) { return hide(target) } if (typeof param === 'object') { target.innerHTML = '' if (0 in param) { for (let i = 0; i in param; i++) { target.appendChild(param[i].cloneNode(true)) } } else { target.appendChild(param.cloneNode(true)) } } else if (param) { target.innerHTML = param } else {} show(target) } export const isModal = () => { return !document.body.classList.contains(swalClasses['toast-shown']) } export const isToast = () => { return document.body.classList.contains(swalClasses['toast-shown']) } export const isLoading = () => { return getPopup().hasAttribute('data-loading') } export const hasClass = (elem, className) => { if (elem.classList) { return elem.classList.contains(className) } return false } export const focusInput = (input) => { input.focus() // place cursor at end of text in text input if (input.type !== 'file') { // http://stackoverflow.com/a/2345915/1331425 const val = input.value input.value = '' input.value = val } } const addOrRemoveClass = (target, classList, add) => { if (!target || !classList) { return } if (typeof classList === 'string') { classList = classList.split(/\s+/).filter(Boolean) } classList.forEach((className) => { if (target.forEach) { target.forEach((elem) => { add ? elem.classList.add(className) : elem.classList.remove(className) }) } else { add ? target.classList.add(className) : target.classList.remove(className) } }) } export const addClass = (target, classList) => { addOrRemoveClass(target, classList, true) } export const removeClass = (target, classList) => { addOrRemoveClass(target, classList, false) } export const getChildByClass = (elem, className) => { for (let i = 0; i < elem.childNodes.length; i++) { if (hasClass(elem.childNodes[i], className)) { return elem.childNodes[i] } } } export const show = (elem) => { elem.style.opacity = '' elem.style.display = (elem.id === swalClasses.content) ? 'block' : 'flex' } export const hide = (elem) => { elem.style.opacity = '' elem.style.display = 'none' } export const empty = (elem) => { while (elem.firstChild) { elem.removeChild(elem.firstChild) } } // borrowed from jquery $(elem).is(':visible') implementation export const isVisible = (elem) => elem && (elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length) export const removeStyleProperty = (elem, property) => { if (elem.style.removeProperty) { elem.style.removeProperty(property) } else { elem.style.removeAttribute(property) } } export const animationEndEvent = (() => { // Prevent run in Node env if (isNodeEnv()) { return false } const testEl = document.createElement('div') const transEndEventNames = { 'WebkitAnimation': 'webkitAnimationEnd', 'OAnimation': 'oAnimationEnd oanimationend', 'animation': 'animationend' } for (const i in transEndEventNames) { if (transEndEventNames.hasOwnProperty(i) && typeof testEl.style[i] !== 'undefined') { return transEndEventNames[i] } } return false })() // Reset previous window keydown handler and focued element export const resetPrevState = () => { if (states.previousActiveElement && states.previousActiveElement.focus) { let x = window.scrollX let y = window.scrollY states.previousActiveElement.focus() if (typeof x !== 'undefined' && typeof y !== 'undefined') { // IE doesn't have scrollX/scrollY support window.scrollTo(x, y) } } } // Measure width of scrollbar // https://github.com/twbs/bootstrap/blob/master/js/modal.js#L279-L286 export const measureScrollbar = () => { const supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints if (supportsTouch) { return 0 } const scrollDiv = document.createElement('div') scrollDiv.style.width = '50px' scrollDiv.style.height = '50px' scrollDiv.style.overflow = 'scroll' document.body.appendChild(scrollDiv) const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth document.body.removeChild(scrollDiv) return scrollbarWidth } /** * Inject a string of CSS into the page header * * @param {String} css */ export const injectCSS = (css = '') => { // Prevent run in Node env if (isNodeEnv()) { return false } let head = document.head || document.getElementsByTagName('head')[0] let style = document.createElement('style') style.type = 'text/css' head.appendChild(style) if (style.styleSheet) { style.styleSheet.cssText = css } else { style.appendChild(document.createTextNode(css)) } }