migrate to new git

This commit is contained in:
2025-08-29 01:27:25 +08:00
parent 946eb9961e
commit af2c152ef6
8623 changed files with 1000453 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
export const DismissReason = Object.freeze({
cancel: 'cancel',
backdrop: 'backdrop',
close: 'close',
esc: 'esc',
timer: 'timer'
})

View File

@@ -0,0 +1,51 @@
export default class Timer {
constructor (callback, delay) {
this.callback = callback
this.remaining = delay
this.running = false
this.start()
}
start () {
if (!this.running) {
this.running = true
this.started = new Date()
this.id = setTimeout(this.callback, this.remaining)
}
return this.remaining
}
stop () {
if (this.running) {
this.running = false
clearTimeout(this.id)
this.remaining -= new Date() - this.started
}
return this.remaining
}
increase (n) {
const running = this.running
if (running) {
this.stop()
}
this.remaining += n
if (running) {
this.start()
}
return this.remaining
}
getTimerLeft () {
if (this.running) {
this.stop()
this.start()
}
return this.remaining
}
isRunning () {
return this.running
}
}

View File

@@ -0,0 +1,34 @@
import { getContainer } from './dom/getters.js'
import { contains } from './dom/domUtils.js'
import { toArray } from './utils.js'
// 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
// readers list of elements (headings, form controls, landmarks, etc.) in the document.
export const setAriaHidden = () => {
const bodyChildren = toArray(document.body.children)
bodyChildren.forEach(el => {
if (el === getContainer() || contains(el, getContainer())) {
return
}
if (el.hasAttribute('aria-hidden')) {
el.setAttribute('data-previous-aria-hidden', el.getAttribute('aria-hidden'))
}
el.setAttribute('aria-hidden', 'true')
})
}
export const unsetAriaHidden = () => {
const bodyChildren = toArray(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')
}
})
}

View File

@@ -0,0 +1,88 @@
export const swalPrefix = 'swal2-'
export const prefix = (items) => {
const result = {}
for (const i in items) {
result[items[i]] = swalPrefix + items[i]
}
return result
}
export const swalClasses = prefix([
'container',
'shown',
'height-auto',
'iosfix',
'popup',
'modal',
'no-backdrop',
'no-transition',
'toast',
'toast-shown',
'toast-column',
'show',
'hide',
'close',
'title',
'header',
'content',
'html-container',
'actions',
'confirm',
'cancel',
'footer',
'icon',
'icon-content',
'image',
'input',
'file',
'range',
'select',
'radio',
'checkbox',
'label',
'textarea',
'inputerror',
'validation-message',
'progress-steps',
'active-progress-step',
'progress-step',
'progress-step-line',
'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',
])
export const iconTypes = prefix([
'success',
'warning',
'info',
'question',
'error'
])

View File

@@ -0,0 +1,13 @@
export default {
email: (string, validationMessage) => {
return /^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,24}$/.test(string)
? Promise.resolve()
: Promise.resolve(validationMessage || 'Invalid email address')
},
url: (string, validationMessage) => {
// taken from https://stackoverflow.com/a/3809435 with a small change from #1306
return /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$/.test(string)
? Promise.resolve()
: Promise.resolve(validationMessage || 'Invalid URL')
}
}

View File

@@ -0,0 +1,23 @@
import { isNodeEnv } from '../isNodeEnv.js'
export const animationEndEvent = (() => {
// Prevent run in Node env
/* istanbul ignore if */
if (isNodeEnv()) {
return false
}
const testEl = document.createElement('div')
const transEndEventNames = {
WebkitAnimation: 'webkitAnimationEnd',
OAnimation: 'oAnimationEnd oanimationend',
animation: 'animationend'
}
for (const i in transEndEventNames) {
if (Object.prototype.hasOwnProperty.call(transEndEventNames, i) && typeof testEl.style[i] !== 'undefined') {
return transEndEventNames[i]
}
}
return false
})()

View File

@@ -0,0 +1,195 @@
import { getTimerProgressBar } from './getters.js'
import { swalClasses, iconTypes } from '../classes.js'
import { toArray, objectValues, warn } from '../utils.js'
// Remember state in cases where opening and handling a modal will fiddle with it.
export const states = {
previousBodyPadding: null
}
export const setInnerHtml = (elem, html) => { // #1926
elem.textContent = ''
if (html) {
const parser = new DOMParser()
const parsed = parser.parseFromString(html, `text/html`)
toArray(parsed.querySelector('head').childNodes).forEach((child) => {
elem.appendChild(child)
})
toArray(parsed.querySelector('body').childNodes).forEach((child) => {
elem.appendChild(child)
})
}
}
export 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
}
const removeCustomClasses = (elem, params) => {
toArray(elem.classList).forEach(className => {
if (
!objectValues(swalClasses).includes(className) &&
!objectValues(iconTypes).includes(className) &&
!objectValues(params.showClass).includes(className)
) {
elem.classList.remove(className)
}
})
}
export const applyCustomClass = (elem, params, className) => {
removeCustomClasses(elem, params)
if (params.customClass && params.customClass[className]) {
if (typeof params.customClass[className] !== 'string' && !params.customClass[className].forEach) {
return warn(`Invalid type of customClass.${className}! Expected string or iterable object, got "${typeof params.customClass[className]}"`)
}
addClass(elem, params.customClass[className])
}
}
export function getInput (content, inputType) {
if (!inputType) {
return null
}
switch (inputType) {
case 'select':
case 'textarea':
case 'file':
return getChildByClass(content, swalClasses[inputType])
case 'checkbox':
return content.querySelector(`.${swalClasses.checkbox} input`)
case 'radio':
return content.querySelector(`.${swalClasses.radio} input:checked`) ||
content.querySelector(`.${swalClasses.radio} input:first-child`)
case 'range':
return content.querySelector(`.${swalClasses.range} input`)
default:
return getChildByClass(content, swalClasses.input)
}
}
export 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
}
}
export const toggleClass = (target, classList, condition) => {
if (!target || !classList) {
return
}
if (typeof classList === 'string') {
classList = classList.split(/\s+/).filter(Boolean)
}
classList.forEach((className) => {
if (target.forEach) {
target.forEach((elem) => {
condition ? elem.classList.add(className) : elem.classList.remove(className)
})
} else {
condition ? target.classList.add(className) : target.classList.remove(className)
}
})
}
export const addClass = (target, classList) => {
toggleClass(target, classList, true)
}
export const removeClass = (target, classList) => {
toggleClass(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 applyNumericalStyle = (elem, property, value) => {
if (value || parseInt(value) === 0) {
elem.style[property] = (typeof value === 'number') ? `${value}px` : value
} else {
elem.style.removeProperty(property)
}
}
export const show = (elem, display = 'flex') => {
elem.style.opacity = ''
elem.style.display = display
}
export const hide = (elem) => {
elem.style.opacity = ''
elem.style.display = 'none'
}
export const toggle = (elem, condition, display) => {
condition ? show(elem, display) : hide(elem)
}
// borrowed from jquery $(elem).is(':visible') implementation
export const isVisible = (elem) => !!(elem && (elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length))
/* istanbul ignore next */
export const isScrollable = (elem) => !!(elem.scrollHeight > elem.clientHeight)
// borrowed from https://stackoverflow.com/a/46352119
export 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
}
export const contains = (haystack, needle) => {
if (typeof haystack.contains === 'function') {
return haystack.contains(needle)
}
}
export const animateTimerProgressBar = (timer, reset = false) => {
const timerProgressBar = getTimerProgressBar()
if (isVisible(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)
}
}
export 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 = parseInt(timerProgressBarWidth / timerProgressBarFullWidth * 100)
timerProgressBar.style.removeProperty('transition')
timerProgressBar.style.width = `${timerProgressBarPercent}%`
}

View File

@@ -0,0 +1,105 @@
import { swalClasses } from '../classes.js'
import { uniqueArray, toArray } from '../utils.js'
import { isVisible } from './domUtils.js'
export const getContainer = () => document.body.querySelector(`.${swalClasses.container}`)
export const elementBySelector = (selectorString) => {
const container = getContainer()
return container ? container.querySelector(selectorString) : null
}
const elementByClass = (className) => {
return elementBySelector(`.${className}`)
}
export const getPopup = () => elementByClass(swalClasses.popup)
export const getIcons = () => {
const popup = getPopup()
return toArray(popup.querySelectorAll(`.${swalClasses.icon}`))
}
export const getIcon = () => {
const visibleIcon = getIcons().filter(icon => isVisible(icon))
return visibleIcon.length ? visibleIcon[0] : null
}
export const getTitle = () => elementByClass(swalClasses.title)
export const getContent = () => elementByClass(swalClasses.content)
export const getHtmlContainer = () => elementByClass(swalClasses['html-container'])
export const getImage = () => elementByClass(swalClasses.image)
export const getProgressSteps = () => elementByClass(swalClasses['progress-steps'])
export const getValidationMessage = () => elementByClass(swalClasses['validation-message'])
export const getConfirmButton = () => elementBySelector(`.${swalClasses.actions} .${swalClasses.confirm}`)
export const getCancelButton = () => elementBySelector(`.${swalClasses.actions} .${swalClasses.cancel}`)
export const getActions = () => elementByClass(swalClasses.actions)
export const getHeader = () => elementByClass(swalClasses.header)
export const getFooter = () => elementByClass(swalClasses.footer)
export const getTimerProgressBar = () => elementByClass(swalClasses['timer-progress-bar'])
export 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
`
export const getFocusableElements = () => {
const focusableElementsWithTabindex = toArray(
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 = toArray(
getPopup().querySelectorAll(focusable)
).filter(el => el.getAttribute('tabindex') !== '-1')
return uniqueArray(focusableElementsWithTabindex.concat(otherFocusableElements)).filter(el => isVisible(el))
}
export const isModal = () => {
return !isToast() && !document.body.classList.contains(swalClasses['no-backdrop'])
}
export const isToast = () => {
return document.body.classList.contains(swalClasses['toast-shown'])
}
export const isLoading = () => {
return getPopup().hasAttribute('data-loading')
}

View File

@@ -0,0 +1,7 @@
export * from './domUtils.js'
export * from './init.js'
export * from './getters.js'
export * from './parseHtmlToContainer.js'
export * from './animationEndEvent.js'
export * from './measureScrollbar.js'
export * from './renderers/render.js'

View File

@@ -0,0 +1,148 @@
import { swalClasses, iconTypes } from '../classes.js'
import { getContainer, getPopup, getContent } from './getters.js'
import { addClass, removeClass, getChildByClass, setInnerHtml } from './domUtils.js'
import { isNodeEnv } from '../isNodeEnv.js'
import { error } from '../utils.js'
import sweetAlert from '../../sweetalert2.js'
const sweetHTML = `
<div aria-labelledby="${swalClasses.title}" aria-describedby="${swalClasses.content}" class="${swalClasses.popup}" tabindex="-1">
<div class="${swalClasses.header}">
<ul class="${swalClasses['progress-steps']}"></ul>
<div class="${swalClasses.icon} ${iconTypes.error}"></div>
<div class="${swalClasses.icon} ${iconTypes.question}"></div>
<div class="${swalClasses.icon} ${iconTypes.warning}"></div>
<div class="${swalClasses.icon} ${iconTypes.info}"></div>
<div class="${swalClasses.icon} ${iconTypes.success}"></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}" class="${swalClasses['html-container']}"></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" />
<span class="${swalClasses.label}"></span>
</label>
<textarea class="${swalClasses.textarea}"></textarea>
<div class="${swalClasses['validation-message']}" id="${swalClasses['validation-message']}"></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 class="${swalClasses['timer-progress-bar-container']}">
<div class="${swalClasses['timer-progress-bar']}"></div>
</div>
</div>
`.replace(/(^|\n)\s*/g, '')
const resetOldContainer = () => {
const oldContainer = getContainer()
if (!oldContainer) {
return false
}
oldContainer.parentNode.removeChild(oldContainer)
removeClass(
[document.documentElement, document.body],
[
swalClasses['no-backdrop'],
swalClasses['toast-shown'],
swalClasses['has-column']
]
)
return true
}
let oldInputVal // IE11 workaround, see #1109 for details
const resetValidationMessage = (e) => {
if (sweetAlert.isVisible() && oldInputVal !== e.target.value) {
sweetAlert.resetValidationMessage()
}
oldInputVal = e.target.value
}
const addInputChangeListeners = () => {
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)
input.oninput = resetValidationMessage
file.onchange = resetValidationMessage
select.onchange = resetValidationMessage
checkbox.onchange = resetValidationMessage
textarea.oninput = resetValidationMessage
range.oninput = (e) => {
resetValidationMessage(e)
rangeOutput.value = range.value
}
range.onchange = (e) => {
resetValidationMessage(e)
range.nextSibling.value = range.value
}
}
const getTarget = (target) => typeof target === 'string' ? document.querySelector(target) : target
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')
}
}
const setupRTL = (targetElement) => {
if (window.getComputedStyle(targetElement).direction === 'rtl') {
addClass(getContainer(), swalClasses.rtl)
}
}
/*
* Add modal + backdrop to DOM
*/
export 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()
}

View File

@@ -0,0 +1,132 @@
import * as dom from './index.js'
import { swalClasses } from '../classes.js'
import { getChildByClass } from './domUtils.js'
import { error, isPromise } from '../utils.js'
import { showLoading } from '../../staticMethods/showLoading.js'
export const handleInputOptionsAndValue = (instance, params) => {
if (params.input === 'select' || params.input === 'radio') {
handleInputOptions(instance, params)
} else if (['text', 'email', 'number', 'tel', 'textarea'].includes(params.input) && isPromise(params.inputValue)) {
handleInputValue(instance, params)
}
}
export 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
}
}
const getCheckboxValue = (input) => input.checked ? 1 : 0
const getRadioValue = (input) => input.checked ? input.value : null
const getFileValue = (input) => input.files.length ? (input.getAttribute('multiple') !== null ? input.files : input.files[0]) : null
const handleInputOptions = (instance, params) => {
const content = dom.getContent()
const processInputOptions = (inputOptions) => populateInputOptions[params.input](content, formatInputOptions(inputOptions), params)
if (isPromise(params.inputOptions)) {
showLoading()
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}`)
}
}
const handleInputValue = (instance, params) => {
const input = instance.getInput()
dom.hide(input)
params.inputValue.then((inputValue) => {
input.value = params.input === 'number' ? parseFloat(inputValue) || 0 : `${inputValue}`
dom.show(input)
input.focus()
instance.hideLoading()
})
.catch((err) => {
error(`Error in inputValue promise: ${err}`)
input.value = ''
dom.show(input)
input.focus()
instance.hideLoading()
})
}
const populateInputOptions = {
select: (content, inputOptions, params) => {
const select = getChildByClass(content, swalClasses.select)
inputOptions.forEach(inputOption => {
const optionValue = inputOption[0]
const optionLabel = inputOption[1]
const option = document.createElement('option')
option.value = optionValue
dom.setInnerHtml(option, optionLabel)
if (params.inputValue.toString() === optionValue.toString()) {
option.selected = true
}
select.appendChild(option)
})
select.focus()
},
radio: (content, inputOptions, params) => {
const radio = getChildByClass(content, swalClasses.radio)
inputOptions.forEach(inputOption => {
const radioValue = inputOption[0]
const radioLabel = inputOption[1]
const radioInput = document.createElement('input')
const radioLabelElement = document.createElement('label')
radioInput.type = 'radio'
radioInput.name = swalClasses.radio
radioInput.value = radioValue
if (params.inputValue.toString() === radioValue.toString()) {
radioInput.checked = true
}
const label = document.createElement('span')
dom.setInnerHtml(label, radioLabel)
label.className = swalClasses.label
radioLabelElement.appendChild(radioInput)
radioLabelElement.appendChild(label)
radio.appendChild(radioLabelElement)
})
const radios = radio.querySelectorAll('input')
if (radios.length) {
radios[0].focus()
}
}
}
/**
* Converts `inputOptions` into an array of `[value, label]`s
* @param inputOptions
*/
const formatInputOptions = (inputOptions) => {
const result = []
if (typeof Map !== 'undefined' && inputOptions instanceof Map) {
inputOptions.forEach((value, key) => {
result.push([key, value])
})
} else {
Object.keys(inputOptions).forEach(key => {
result.push([key, inputOptions[key]])
})
}
return result
}

View File

@@ -0,0 +1,12 @@
import { swalClasses } from '../classes.js'
// Measure scrollbar width for padding body during modal show/hide
// https://github.com/twbs/bootstrap/blob/master/js/src/modal.js
export 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
}

View File

@@ -0,0 +1,38 @@
import { setInnerHtml } from './domUtils.js'
export 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)
}
}
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())
}
}
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))
}
}

View File

@@ -0,0 +1,21 @@
import { getPopup } from '../getters.js'
import { renderActions } from './renderActions.js'
import { renderContainer } from './renderContainer.js'
import { renderContent } from './renderContent.js'
import { renderFooter } from './renderFooter.js'
import { renderHeader } from './renderHeader.js'
import { renderPopup } from './renderPopup.js'
export const render = (instance, params) => {
renderPopup(instance, params)
renderContainer(instance, params)
renderHeader(instance, params)
renderContent(instance, params)
renderActions(instance, params)
renderFooter(instance, params)
if (typeof params.onRender === 'function') {
params.onRender(getPopup())
}
}

View File

@@ -0,0 +1,62 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { capitalizeFirstLetter } from '../../utils.js'
export const renderActions = (instance, params) => {
const actions = dom.getActions()
const confirmButton = dom.getConfirmButton()
const cancelButton = dom.getCancelButton()
// Actions (buttons) wrapper
if (!params.showConfirmButton && !params.showCancelButton) {
dom.hide(actions)
}
// Custom class
dom.applyCustomClass(actions, params, 'actions')
// Render confirm button
renderButton(confirmButton, 'confirm', params)
// render Cancel Button
renderButton(cancelButton, 'cancel', params)
if (params.buttonsStyling) {
handleButtonsStyling(confirmButton, cancelButton, params)
} else {
dom.removeClass([confirmButton, cancelButton], swalClasses.styled)
confirmButton.style.backgroundColor = confirmButton.style.borderLeftColor = confirmButton.style.borderRightColor = ''
cancelButton.style.backgroundColor = cancelButton.style.borderLeftColor = cancelButton.style.borderRightColor = ''
}
if (params.reverseButtons) {
confirmButton.parentNode.insertBefore(cancelButton, confirmButton)
}
}
function handleButtonsStyling (confirmButton, cancelButton, params) {
dom.addClass([confirmButton, cancelButton], swalClasses.styled)
// Buttons background colors
if (params.confirmButtonColor) {
confirmButton.style.backgroundColor = params.confirmButtonColor
}
if (params.cancelButtonColor) {
cancelButton.style.backgroundColor = params.cancelButtonColor
}
// Loading state
const confirmButtonBackgroundColor = window.getComputedStyle(confirmButton).getPropertyValue('background-color')
confirmButton.style.borderLeftColor = confirmButtonBackgroundColor
confirmButton.style.borderRightColor = confirmButtonBackgroundColor
}
function renderButton (button, buttonType, params) {
dom.toggle(button, params[`show${capitalizeFirstLetter(buttonType)}Button`], 'inline-block')
dom.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]
dom.applyCustomClass(button, params, `${buttonType}Button`)
dom.addClass(button, params[`${buttonType}ButtonClass`])
}

View File

@@ -0,0 +1,13 @@
import * as dom from '../../dom/index.js'
export const renderCloseButton = (instance, params) => {
const closeButton = dom.getCloseButton()
dom.setInnerHtml(closeButton, params.closeButtonHtml)
// Custom class
dom.applyCustomClass(closeButton, params, 'closeButton')
dom.toggle(closeButton, params.showCloseButton)
closeButton.setAttribute('aria-label', params.closeButtonAriaLabel)
}

View File

@@ -0,0 +1,56 @@
import { swalClasses } from '../../classes.js'
import { warn } from '../../utils.js'
import * as dom from '../../dom/index.js'
function handleBackdropParam (container, backdrop) {
if (typeof backdrop === 'string') {
container.style.background = backdrop
} else if (!backdrop) {
dom.addClass([document.documentElement, document.body], swalClasses['no-backdrop'])
}
}
function handlePositionParam (container, position) {
if (position in swalClasses) {
dom.addClass(container, swalClasses[position])
} else {
warn('The "position" parameter is not valid, defaulting to "center"')
dom.addClass(container, swalClasses.center)
}
}
function handleGrowParam (container, grow) {
if (grow && typeof grow === 'string') {
const growClass = `grow-${grow}`
if (growClass in swalClasses) {
dom.addClass(container, swalClasses[growClass])
}
}
}
export const renderContainer = (instance, params) => {
const container = dom.getContainer()
if (!container) {
return
}
handleBackdropParam(container, params.backdrop)
if (!params.backdrop && params.allowOutsideClick) {
warn('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`')
}
handlePositionParam(container, params.position)
handleGrowParam(container, params.grow)
// Custom class
dom.applyCustomClass(container, params, 'container')
// Set queue step attribute for getQueueStep() method
const queueStep = document.body.getAttribute('data-swal2-queue-step')
if (queueStep) {
container.setAttribute('data-queue-step', queueStep)
document.body.removeAttribute('data-swal2-queue-step')
}
}

View File

@@ -0,0 +1,27 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { renderInput } from './renderInput.js'
export const renderContent = (instance, params) => {
const content = dom.getContent().querySelector(`#${swalClasses.content}`)
// Content as HTML
if (params.html) {
dom.parseHtmlToContainer(params.html, content)
dom.show(content, 'block')
// Content as plain text
} else if (params.text) {
content.textContent = params.text
dom.show(content, 'block')
// No content
} else {
dom.hide(content)
}
renderInput(instance, params)
// Custom class
dom.applyCustomClass(dom.getContent(), params, 'content')
}

View File

@@ -0,0 +1,14 @@
import * as dom from '../../dom/index.js'
export const renderFooter = (instance, params) => {
const footer = dom.getFooter()
dom.toggle(footer, params.footer)
if (params.footer) {
dom.parseHtmlToContainer(params.footer, footer)
}
// Custom class
dom.applyCustomClass(footer, params, 'footer')
}

View File

@@ -0,0 +1,28 @@
import * as dom from '../../dom/index.js'
import { renderCloseButton } from './renderCloseButton.js'
import { renderIcon } from './renderIcon.js'
import { renderImage } from './renderImage.js'
import { renderProgressSteps } from './renderProgressSteps.js'
import { renderTitle } from './renderTitle.js'
export const renderHeader = (instance, params) => {
const header = dom.getHeader()
// Custom class
dom.applyCustomClass(header, params, 'header')
// Progress steps
renderProgressSteps(instance, params)
// Icon
renderIcon(instance, params)
// Image
renderImage(instance, params)
// Title
renderTitle(instance, params)
// Close button
renderCloseButton(instance, params)
}

View File

@@ -0,0 +1,85 @@
import { swalClasses, iconTypes } from '../../classes.js'
import { error } from '../../utils.js'
import * as dom from '../../dom/index.js'
import privateProps from '../../../privateProps.js'
export const renderIcon = (instance, params) => {
const innerParams = privateProps.innerParams.get(instance)
// if the give icon already rendered, apply the custom class without re-rendering the icon
if (innerParams && params.icon === innerParams.icon && dom.getIcon()) {
dom.applyCustomClass(dom.getIcon(), params, 'icon')
return
}
hideAllIcons()
if (!params.icon) {
return
}
if (Object.keys(iconTypes).indexOf(params.icon) !== -1) {
const icon = dom.elementBySelector(`.${swalClasses.icon}.${iconTypes[params.icon]}`)
dom.show(icon)
// Custom or default content
setContent(icon, params)
adjustSuccessIconBackgoundColor()
// Custom class
dom.applyCustomClass(icon, params, 'icon')
// Animate icon
dom.addClass(icon, params.showClass.icon)
} else {
error(`Unknown icon! Expected "success", "error", "warning", "info" or "question", got "${params.icon}"`)
}
}
const hideAllIcons = () => {
const icons = dom.getIcons()
for (let i = 0; i < icons.length; i++) {
dom.hide(icons[i])
}
}
// Adjust success icon background color to match the popup background color
const adjustSuccessIconBackgoundColor = () => {
const popup = dom.getPopup()
const popupBackgroundColor = window.getComputedStyle(popup).getPropertyValue('background-color')
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 setContent = (icon, params) => {
icon.textContent = ''
if (params.iconHtml) {
dom.setInnerHtml(icon, iconContent(params.iconHtml))
} else if (params.icon === 'success') {
dom.setInnerHtml(icon, `
<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>
`)
} else if (params.icon === 'error') {
dom.setInnerHtml(icon, `
<span class="swal2-x-mark">
<span class="swal2-x-mark-line-left"></span>
<span class="swal2-x-mark-line-right"></span>
</span>
`)
} else {
const defaultIconHtml = {
question: '?',
warning: '!',
info: 'i'
}
dom.setInnerHtml(icon, iconContent(defaultIconHtml[params.icon]))
}
}
const iconContent = (content) => `<div class="${swalClasses['icon-content']}">${content}</div>`

View File

@@ -0,0 +1,24 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
export const renderImage = (instance, params) => {
const image = dom.getImage()
if (!params.imageUrl) {
return dom.hide(image)
}
dom.show(image)
// Src, alt
image.setAttribute('src', params.imageUrl)
image.setAttribute('alt', params.imageAlt)
// Width, height
dom.applyNumericalStyle(image, 'width', params.imageWidth)
dom.applyNumericalStyle(image, 'height', params.imageHeight)
// Class
image.className = swalClasses.image
dom.applyCustomClass(image, params, 'image')
}

View File

@@ -0,0 +1,179 @@
import { swalClasses } from '../../classes.js'
import { warn, error, isPromise } from '../../utils.js'
import * as dom from '../../dom/index.js'
import privateProps from '../../../privateProps.js'
const inputTypes = ['input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea']
export const renderInput = (instance, params) => {
const content = dom.getContent()
const innerParams = privateProps.innerParams.get(instance)
const rerender = !innerParams || params.input !== innerParams.input
inputTypes.forEach((inputType) => {
const inputClass = swalClasses[inputType]
const inputContainer = dom.getChildByClass(content, inputClass)
// set attributes
setAttributes(inputType, params.inputAttributes)
// set class
inputContainer.className = inputClass
if (rerender) {
dom.hide(inputContainer)
}
})
if (params.input) {
if (rerender) {
showInput(params)
}
// set custom class
setCustomClass(params)
}
}
const showInput = (params) => {
if (!renderInputType[params.input]) {
return error(`Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "${params.input}"`)
}
const inputContainer = getInputContainer(params.input)
const input = renderInputType[params.input](inputContainer, params)
dom.show(input)
// input autofocus
setTimeout(() => {
dom.focusInput(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)
}
}
}
const setAttributes = (inputType, inputAttributes) => {
const input = dom.getInput(dom.getContent(), inputType)
if (!input) {
return
}
removeAttributes(input)
for (const attr in inputAttributes) {
// Do not set a placeholder for <input type="range">
// it'll crash Edge, #1298
if (inputType === 'range' && attr === 'placeholder') {
continue
}
input.setAttribute(attr, inputAttributes[attr])
}
}
const setCustomClass = (params) => {
const inputContainer = getInputContainer(params.input)
if (params.customClass) {
dom.addClass(inputContainer, params.customClass.input)
}
}
const setInputPlaceholder = (input, params) => {
if (!input.placeholder || params.inputPlaceholder) {
input.placeholder = params.inputPlaceholder
}
}
const getInputContainer = (inputType) => {
const inputClass = swalClasses[inputType] ? swalClasses[inputType] : swalClasses.input
return dom.getChildByClass(dom.getContent(), inputClass)
}
const renderInputType = {}
renderInputType.text =
renderInputType.email =
renderInputType.password =
renderInputType.number =
renderInputType.tel =
renderInputType.url = (input, params) => {
if (typeof params.inputValue === 'string' || typeof params.inputValue === 'number') {
input.value = params.inputValue
} else if (!isPromise(params.inputValue)) {
warn(`Unexpected type of inputValue! Expected "string", "number" or "Promise", got "${typeof params.inputValue}"`)
}
setInputPlaceholder(input, params)
input.type = params.input
return input
}
renderInputType.file = (input, params) => {
setInputPlaceholder(input, params)
return input
}
renderInputType.range = (range, params) => {
const rangeInput = range.querySelector('input')
const rangeOutput = range.querySelector('output')
rangeInput.value = params.inputValue
rangeInput.type = params.input
rangeOutput.value = params.inputValue
return range
}
renderInputType.select = (select, params) => {
select.textContent = ''
if (params.inputPlaceholder) {
const placeholder = document.createElement('option')
dom.setInnerHtml(placeholder, params.inputPlaceholder)
placeholder.value = ''
placeholder.disabled = true
placeholder.selected = true
select.appendChild(placeholder)
}
return select
}
renderInputType.radio = (radio) => {
radio.textContent = ''
return radio
}
renderInputType.checkbox = (checkboxContainer, params) => {
const checkbox = dom.getInput(dom.getContent(), 'checkbox')
checkbox.value = 1
checkbox.id = swalClasses.checkbox
checkbox.checked = Boolean(params.inputValue)
const label = checkboxContainer.querySelector('span')
dom.setInnerHtml(label, params.inputPlaceholder)
return checkboxContainer
}
renderInputType.textarea = (textarea, params) => {
textarea.value = params.inputValue
setInputPlaceholder(textarea, params)
if ('MutationObserver' in window) { // #1699
const initialPopupWidth = parseInt(window.getComputedStyle(dom.getPopup()).width)
const popupPadding = parseInt(window.getComputedStyle(dom.getPopup()).paddingLeft) + parseInt(window.getComputedStyle(dom.getPopup()).paddingRight)
const outputsize = () => {
const contentWidth = textarea.offsetWidth + popupPadding
if (contentWidth > initialPopupWidth) {
dom.getPopup().style.width = `${contentWidth}px`
} else {
dom.getPopup().style.width = null
}
}
new MutationObserver(outputsize).observe(textarea, {
attributes: true, attributeFilter: ['style']
})
}
return textarea
}

View File

@@ -0,0 +1,43 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
export const renderPopup = (instance, params) => {
const popup = dom.getPopup()
// Width
dom.applyNumericalStyle(popup, 'width', params.width)
// Padding
dom.applyNumericalStyle(popup, 'padding', params.padding)
// Background
if (params.background) {
popup.style.background = params.background
}
// Classes
addClasses(popup, params)
}
const addClasses = (popup, params) => {
// Default Class + showClass when updating Swal.update({})
popup.className = `${swalClasses.popup} ${dom.isVisible(popup) ? params.showClass.popup : ''}`
if (params.toast) {
dom.addClass([document.documentElement, document.body], swalClasses['toast-shown'])
dom.addClass(popup, swalClasses.toast)
} else {
dom.addClass(popup, swalClasses.modal)
}
// Custom class
dom.applyCustomClass(popup, params, 'popup')
if (typeof params.customClass === 'string') {
dom.addClass(popup, params.customClass)
}
// Icon class (#1842)
if (params.icon) {
dom.addClass(popup, swalClasses[`icon-${params.icon}`])
}
}

View File

@@ -0,0 +1,50 @@
import { swalClasses } from '../../classes.js'
import { warn } from '../../utils.js'
import * as dom from '../../dom/index.js'
import { getQueueStep } from '../../../staticMethods/queue.js'
const createStepElement = (step) => {
const stepEl = document.createElement('li')
dom.addClass(stepEl, swalClasses['progress-step'])
dom.setInnerHtml(stepEl, step)
return stepEl
}
const createLineElement = (params) => {
const lineEl = document.createElement('li')
dom.addClass(lineEl, swalClasses['progress-step-line'])
if (params.progressStepsDistance) {
lineEl.style.width = params.progressStepsDistance
}
return lineEl
}
export const renderProgressSteps = (instance, params) => {
const progressStepsContainer = dom.getProgressSteps()
if (!params.progressSteps || params.progressSteps.length === 0) {
return dom.hide(progressStepsContainer)
}
dom.show(progressStepsContainer)
progressStepsContainer.textContent = ''
const currentProgressStep = parseInt(params.currentProgressStep === undefined ? getQueueStep() : params.currentProgressStep)
if (currentProgressStep >= params.progressSteps.length) {
warn(
'Invalid currentProgressStep parameter, it should be less than progressSteps.length ' +
'(currentProgressStep like JS arrays starts from 0)'
)
}
params.progressSteps.forEach((step, index) => {
const stepEl = createStepElement(step)
progressStepsContainer.appendChild(stepEl)
if (index === currentProgressStep) {
dom.addClass(stepEl, swalClasses['active-progress-step'])
}
if (index !== params.progressSteps.length - 1) {
const lineEl = createLineElement(step)
progressStepsContainer.appendChild(lineEl)
}
})
}

View File

@@ -0,0 +1,18 @@
import * as dom from '../../dom/index.js'
export const renderTitle = (instance, params) => {
const title = dom.getTitle()
dom.toggle(title, params.title || params.titleText)
if (params.title) {
dom.parseHtmlToContainer(params.title, title)
}
if (params.titleText) {
title.innerText = params.titleText
}
// Custom class
dom.applyCustomClass(title, params, 'title')
}

View File

@@ -0,0 +1,29 @@
/* istanbul ignore file */
import * as dom from './dom/index.js'
// https://stackoverflow.com/a/21825207
const isIE11 = () => !!window.MSInputMethodContext && !!document.documentMode
// Fix IE11 centering sweetalert2/issues/933
const fixVerticalPositionIE = () => {
const container = dom.getContainer()
const popup = dom.getPopup()
container.style.removeProperty('align-items')
if (popup.offsetTop < 0) {
container.style.alignItems = 'flex-start'
}
}
export const IEfix = () => {
if (typeof window !== 'undefined' && isIE11()) {
fixVerticalPositionIE()
window.addEventListener('resize', fixVerticalPositionIE)
}
}
export const undoIEfix = () => {
if (typeof window !== 'undefined' && isIE11()) {
window.removeEventListener('resize', fixVerticalPositionIE)
}
}

View File

@@ -0,0 +1,43 @@
/* istanbul ignore file */
import * as dom from './dom/index.js'
import { swalClasses } from '../utils/classes.js'
// Fix iOS scrolling http://stackoverflow.com/q/39626302
export const iOSfix = () => {
const iOS = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
if (iOS && !dom.hasClass(document.body, swalClasses.iosfix)) {
const offset = document.body.scrollTop
document.body.style.top = `${offset * -1}px`
dom.addClass(document.body, swalClasses.iosfix)
lockBodyScroll()
}
}
const lockBodyScroll = () => { // #1246
const container = dom.getContainer()
let preventTouchMove
container.ontouchstart = (e) => {
preventTouchMove =
e.target === container ||
(
!dom.isScrollable(container) &&
e.target.tagName !== 'INPUT' // #1603
)
}
container.ontouchmove = (e) => {
if (preventTouchMove) {
e.preventDefault()
e.stopPropagation()
}
}
}
export const undoIOSfix = () => {
if (dom.hasClass(document.body, swalClasses.iosfix)) {
const offset = parseInt(document.body.style.top, 10)
dom.removeClass(document.body, swalClasses.iosfix)
document.body.style.top = ''
document.body.scrollTop = (offset * -1)
}
}

View File

@@ -0,0 +1,2 @@
// Detect Node env
export const isNodeEnv = () => typeof window === 'undefined' || typeof document === 'undefined'

View File

@@ -0,0 +1,84 @@
import * as dom from './dom/index.js'
import { swalClasses } from './classes.js'
import { fixScrollbar } from './scrollbarFix.js'
import { iOSfix } from './iosFix.js'
import { IEfix } from './ieFix.js'
import { setAriaHidden } from './aria.js'
import globalState from '../globalState.js'
/**
* Open popup, add necessary classes and styles, fix scrollbar
*
* @param {Array} params
*/
export const openPopup = (params) => {
const container = dom.getContainer()
const popup = dom.getPopup()
if (typeof params.onBeforeOpen === 'function') {
params.onBeforeOpen(popup)
}
addClasses(container, popup, params)
// scrolling is 'hidden' until animation is done, after that 'auto'
setScrollingVisibility(container, popup)
if (dom.isModal()) {
fixScrollContainer(container, params.scrollbarPadding)
}
if (!dom.isToast() && !globalState.previousActiveElement) {
globalState.previousActiveElement = document.activeElement
}
if (typeof params.onOpen === 'function') {
setTimeout(() => params.onOpen(popup))
}
dom.removeClass(container, swalClasses['no-transition'])
}
function swalOpenAnimationFinished (event) {
const popup = dom.getPopup()
if (event.target !== popup) {
return
}
const container = dom.getContainer()
popup.removeEventListener(dom.animationEndEvent, swalOpenAnimationFinished)
container.style.overflowY = 'auto'
}
const setScrollingVisibility = (container, popup) => {
if (dom.animationEndEvent && dom.hasCssAnimation(popup)) {
container.style.overflowY = 'hidden'
popup.addEventListener(dom.animationEndEvent, swalOpenAnimationFinished)
} else {
container.style.overflowY = 'auto'
}
}
const fixScrollContainer = (container, scrollbarPadding) => {
iOSfix()
IEfix()
setAriaHidden()
if (scrollbarPadding) {
fixScrollbar()
}
// sweetalert2/issues/1247
setTimeout(() => {
container.scrollTop = 0
})
}
const addClasses = (container, popup, params) => {
dom.addClass(container, params.showClass.backdrop)
dom.show(popup)
// Animate popup right after showing it
dom.addClass(popup, params.showClass.popup)
dom.addClass([document.documentElement, document.body], swalClasses.shown)
if (params.heightAuto && params.backdrop && !params.toast) {
dom.addClass([document.documentElement, document.body], swalClasses['height-auto'])
}
}

View File

@@ -0,0 +1,181 @@
import { warn, warnAboutDepreation } from '../utils/utils.js'
export const defaultParams = {
title: '',
titleText: '',
text: '',
html: '',
footer: '',
icon: undefined,
iconHtml: undefined,
toast: false,
animation: true,
showClass: {
popup: 'swal2-show',
backdrop: 'swal2-backdrop-show',
icon: 'swal2-icon-show',
},
hideClass: {
popup: 'swal2-hide',
backdrop: 'swal2-backdrop-hide',
icon: 'swal2-icon-hide',
},
customClass: undefined,
target: 'body',
backdrop: true,
heightAuto: true,
allowOutsideClick: true,
allowEscapeKey: true,
allowEnterKey: true,
stopKeydownPropagation: true,
keydownListenerCapture: false,
showConfirmButton: true,
showCancelButton: false,
preConfirm: undefined,
confirmButtonText: 'OK',
confirmButtonAriaLabel: '',
confirmButtonColor: undefined,
cancelButtonText: 'Cancel',
cancelButtonAriaLabel: '',
cancelButtonColor: undefined,
buttonsStyling: true,
reverseButtons: false,
focusConfirm: true,
focusCancel: false,
showCloseButton: false,
closeButtonHtml: '&times;',
closeButtonAriaLabel: 'Close this dialog',
showLoaderOnConfirm: false,
imageUrl: undefined,
imageWidth: undefined,
imageHeight: undefined,
imageAlt: '',
timer: undefined,
timerProgressBar: false,
width: undefined,
padding: undefined,
background: undefined,
input: undefined,
inputPlaceholder: '',
inputValue: '',
inputOptions: {},
inputAutoTrim: true,
inputAttributes: {},
inputValidator: undefined,
validationMessage: undefined,
grow: false,
position: 'center',
progressSteps: [],
currentProgressStep: undefined,
progressStepsDistance: undefined,
onBeforeOpen: undefined,
onOpen: undefined,
onRender: undefined,
onClose: undefined,
onAfterClose: undefined,
onDestroy: undefined,
scrollbarPadding: true
}
export const updatableParams = [
'title',
'titleText',
'text',
'html',
'icon',
'hideClass',
'customClass',
'allowOutsideClick',
'allowEscapeKey',
'showConfirmButton',
'showCancelButton',
'confirmButtonText',
'confirmButtonAriaLabel',
'confirmButtonColor',
'cancelButtonText',
'cancelButtonAriaLabel',
'cancelButtonColor',
'buttonsStyling',
'reverseButtons',
'imageUrl',
'imageWidth',
'imageHeight',
'imageAlt',
'progressSteps',
'currentProgressStep'
]
export const deprecatedParams = {
animation: 'showClass" and "hideClass',
}
const toastIncompatibleParams = [
'allowOutsideClick',
'allowEnterKey',
'backdrop',
'focusConfirm',
'focusCancel',
'heightAuto',
'keydownListenerCapture'
]
/**
* Is valid parameter
* @param {String} paramName
*/
export const isValidParameter = (paramName) => {
return Object.prototype.hasOwnProperty.call(defaultParams, paramName)
}
/**
* Is valid parameter for Swal.update() method
* @param {String} paramName
*/
export const isUpdatableParameter = (paramName) => {
return updatableParams.indexOf(paramName) !== -1
}
/**
* Is deprecated parameter
* @param {String} paramName
*/
export const isDeprecatedParameter = (paramName) => {
return deprecatedParams[paramName]
}
const checkIfParamIsValid = (param) => {
if (!isValidParameter(param)) {
warn(`Unknown parameter "${param}"`)
}
}
const checkIfToastParamIsValid = (param) => {
if (toastIncompatibleParams.includes(param)) {
warn(`The parameter "${param}" is incompatible with toasts`)
}
}
const checkIfParamIsDeprecated = (param) => {
if (isDeprecatedParameter(param)) {
warnAboutDepreation(param, isDeprecatedParameter(param))
}
}
/**
* Show relevant warnings for given params
*
* @param params
*/
export const showWarningsForParams = (params) => {
for (const param in params) {
checkIfParamIsValid(param)
if (params.toast) {
checkIfToastParamIsValid(param)
}
checkIfParamIsDeprecated(param)
}
}
export default defaultParams

View File

@@ -0,0 +1,21 @@
import * as dom from './dom/index.js'
export const fixScrollbar = () => {
// for queues, do not do this more than once
if (dom.states.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
dom.states.previousBodyPadding = parseInt(window.getComputedStyle(document.body).getPropertyValue('padding-right'))
document.body.style.paddingRight = `${dom.states.previousBodyPadding + dom.measureScrollbar()}px`
}
}
export const undoScrollbar = () => {
if (dom.states.previousBodyPadding !== null) {
document.body.style.paddingRight = `${dom.states.previousBodyPadding}px`
dom.states.previousBodyPadding = null
}
}

View File

@@ -0,0 +1,60 @@
import { warn, callIfFunction } from './utils.js'
import * as dom from './dom/index.js'
import defaultInputValidators from './defaultInputValidators.js'
function setDefaultInputValidators (params) {
// Use default `inputValidator` for supported input types if not provided
if (!params.inputValidator) {
Object.keys(defaultInputValidators).forEach((key) => {
if (params.input === key) {
params.inputValidator = defaultInputValidators[key]
}
})
}
}
function validateCustomTargetElement (params) {
// Determine if the custom target element is valid
if (
!params.target ||
(typeof params.target === 'string' && !document.querySelector(params.target)) ||
(typeof params.target !== 'string' && !params.target.appendChild)
) {
warn('Target parameter is not valid, defaulting to "body"')
params.target = 'body'
}
}
/**
* Set type, text and actions on popup
*
* @param params
* @returns {boolean}
*/
export default function setParameters (params) {
setDefaultInputValidators(params)
// showLoaderOnConfirm && preConfirm
if (params.showLoaderOnConfirm && !params.preConfirm) {
warn(
'showLoaderOnConfirm is set to true, but preConfirm is not defined.\n' +
'showLoaderOnConfirm should be used together with preConfirm, see usage example:\n' +
'https://sweetalert2.github.io/#ajax-request'
)
}
// params.animation will be actually used in renderPopup.js
// but in case when params.animation is a function, we need to call that function
// before popup (re)initialization, so it'll be possible to check Swal.isVisible()
// inside the params.animation function
params.animation = callIfFunction(params.animation)
validateCustomTargetElement(params)
// Replace newlines with <br> in title
if (typeof params.title === 'string') {
params.title = params.title.split('\n').join('<br />')
}
dom.init(params)
}

View File

@@ -0,0 +1,83 @@
export const consolePrefix = 'SweetAlert2:'
/**
* Filter the unique values into a new array
* @param arr
*/
export const uniqueArray = (arr) => {
const result = []
for (let i = 0; i < arr.length; i++) {
if (result.indexOf(arr[i]) === -1) {
result.push(arr[i])
}
}
return result
}
/**
* Capitalize the first letter of a string
* @param str
*/
export const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1)
/**
* Returns the array ob object values (Object.values isn't supported in IE11)
* @param obj
*/
export const objectValues = (obj) => Object.keys(obj).map(key => obj[key])
/**
* Convert NodeList to Array
* @param nodeList
*/
export const toArray = (nodeList) => Array.prototype.slice.call(nodeList)
/**
* Standardise console warnings
* @param message
*/
export const warn = (message) => {
console.warn(`${consolePrefix} ${message}`)
}
/**
* Standardise console errors
* @param message
*/
export const error = (message) => {
console.error(`${consolePrefix} ${message}`)
}
/**
* Private global state for `warnOnce`
* @type {Array}
* @private
*/
const previousWarnOnceMessages = []
/**
* Show a console warning, but only if it hasn't already been shown
* @param message
*/
export const warnOnce = (message) => {
if (!previousWarnOnceMessages.includes(message)) {
previousWarnOnceMessages.push(message)
warn(message)
}
}
/**
* Show a one-time console warning about deprecated params/methods
*/
export const warnAboutDepreation = (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 arg
*/
export const callIfFunction = (arg) => typeof arg === 'function' ? arg() : arg
export const isPromise = (arg) => arg && Promise.resolve(arg) === arg