This commit is contained in:
David Štaleker
2024-02-23 12:56:54 +01:00
parent 18bac3de1c
commit 28d1630749
1388 changed files with 558637 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Tristan Edwards & Limon Monte
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,133 @@
<a href="https://sweetalert2.github.io/">
<img src="./assets/swal2-logo.png" width="498" alt="SweetAlert2">
</a>
A beautiful, responsive, customizable, accessible (WAI-ARIA) replacement <br> for JavaScript's popup boxes. Zero dependencies.
---
### [Installation](https://sweetalert2.github.io/#download) | [Usage](https://sweetalert2.github.io/#usage) | [Examples](https://sweetalert2.github.io/#examples) | [Recipe gallery](https://sweetalert2.github.io/recipe-gallery/) | [Themes](https://github.com/sweetalert2/sweetalert2-themes) | [React](https://github.com/sweetalert2/sweetalert2-react-content) | [Angular](https://github.com/sweetalert2/ngx-sweetalert2)
---
:moneybag: [Get $200 in free credits with DigitalOcean!](https://m.do.co/c/12907f2ba0bf)
---
> [!IMPORTANT]
> As a consequence of the illegal war in Ukraine, the behavior of this repository and related npm package [sweetalert2](https://www.npmjs.com/package/sweetalert2) is different for `.ru`, `.su`, `.by`, and `.рф` domain zones.
>
> Including this software in any domain in `.ru`, `.su`, `.by`, and `.рф` domain zones will block the website navigation and play the national anthem of Ukraine.
>
> This behavior is classified as [protestware](https://snyk.io/blog/protestware-open-source-types-impact/) and this project is listed in [GitHub Advisory Database](https://github.com/advisories/GHSA-mrr8-v49w-3333) and [Snyk Vulnerability DB](https://security.snyk.io/package/npm/sweetalert2/11.5.2).
Sponsors
--------
For all questions related to sponsorship please get in touch with me via email sweetalert2@gmail.com
<table>
<tr>
<td align="center"><a href="SPONSORS.md#sponsors"><img src="https://sweetalert2.github.io/images/plus.png" width="80"><br>Become a sponsor</a></td>
<td align="center"><a href="https://insadown.com/"><img src="https://sweetalert2.github.io/images/sponsors/insadown.png" width="80"><br>InsaDown</a></td>
<td align="center"><a href="https://dlxplugins.com/"><img src="https://avatars.githubusercontent.com/u/106675919?s=200&v=4" width="80"><br>DLX Plugins</a></td>
<td align="center"><a href="https://github.com/tiagostutz"><img src="https://avatars0.githubusercontent.com/u/3986989?s=80&v=4" width="80"><br>Tiago de Oliveira Stutz</a></td>
<td align="center"><a href="https://refermate.com/"><img src="https://sweetalert2.github.io/images/sponsors/refermate.png" width="80"><br>Refermate</a>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://roboflow.com/"><img src="https://sweetalert2.github.io/images/sponsors/roboflow.png" width="80"><br>Roboflow</a>
<td align="center"><a href="https://www.zezelife.com/"><img src="https://sweetalert2.github.io/images/sponsors/zezelife.png" width="80"><br>ZezeLife</a>
<td align="center"><a href="https://www.serpempire.com/"><img src="https://sweetalert2.github.io/images/sponsors/serpempire.png" width="80"><br>SERP Empire</a>
<td align="center"><a href="https://www.realspyapps.com/"><img src="https://sweetalert2.github.io/images/sponsors/realspyapps.jpeg" width="80"><br>Real Spy Apps</a>
<td align="center"><a href="https://celltrackingapps.com/"><img src="https://sweetalert2.github.io/images/sponsors/celltrackingapps.png" width="80"><br>Phone Tracking Apps</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://www.mybitcoinslots.com/"><img src="https://sweetalert2.github.io/images/sponsors/mybitcoinslots.png" width="80"><br>My Bitcoin slots</a></td>
<td align="center"><a href="https://www.vegega.com/"><img src="https://sweetalert2.github.io/images/sponsors/vegega.jpeg" width="80"><br>Metal Raised Garden Bed</a></td>
</tr>
</table>
NSFW Sponsors
-------------
<table>
<tr>
<td align="center"><a href="SPONSORS.md#sponsors"><img src="https://sweetalert2.github.io/images/plus.png" width="80"><br>Become a NSFW sponsor</a></td>
<td align="center"><a href="https://nexromance.com/playstore/index.php?sweetalert"><img src="https://sweetalert2.github.io/images/sponsors/chatforsex.jpg" width="80"><br>ChatGPT for Sex</a></td>
<td align="center"><a href="https://pinktoys.co.uk/pocket-pussy/"><img src="https://sweetalert2.github.io/images/sponsors/pinktoys.png" width="80"><br>Pocket pussy</a></td>
<td align="center"><a href="https://www.xspacecup.com/"><img src="https://sweetalert2.github.io/images/sponsors/xspacecup.png" width="80"><br>XspaceCup - Top Male <br> Masturbator Brand</a></td>
<td align="center"><a href="https://vlixa.com/"><img src="https://sweetalert2.github.io/images/sponsors/vlixa.png" width="80"><br>Vlixa</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://taboodude.com/"><img src="https://sweetalert2.github.io/images/sponsors/taboodude.png" width="80"><br>TabooDude</a></td>
<td align="center"><a href="https://sexsitoys.com/lovense-games/"><img src="https://sweetalert2.github.io/images/sponsors/sexsitoys.jpg" width="80"><br>Sexsi Toys</a></td>
<td align="center"><a href="https://www.cheapestsexdolls.com"><img src="https://sweetalert2.github.io/images/sponsors/cheapestsexdolls.png" width="80"><br>CheapestSexDolls</a></td>
<td align="center"><a href="https://www.bestblowjobmachines.com/"><img src="https://sweetalert2.github.io/images/sponsors/best-blowjob-machines.jpg" width="80"><br>Best Blowjob Machines</a></td>
<td align="center"><a href="https://escortsear.ch/"><img src="https://sweetalert2.github.io/images/sponsors/escortsearch.jpg" width="80"><br>EscortSearch</a></td>
<td align="center"><a href="https://nakedoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/nakedoll.png" width="80"><br>NakeDoll</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://readysetcam.com/"><img src="https://sweetalert2.github.io/images/sponsors/readysetcam.png" width="80"><br>Ready Set Cam</a></td>
<td align="center"><a href="https://www.onahole.com/"><img src="https://sweetalert2.github.io/images/sponsors/onahole.png" width="80"><br>hentai sex toys</a></td>
<td align="center"><a href="https://blowupdollshop.com/inflatable-sex-doll/"><img src="https://sweetalert2.github.io/images/sponsors/blowupdollshop.png" width="80"><br>Inflatable sex doll</a></td>
<td align="center"><a href="https://sexdollsmarket.com/"><img src="https://sweetalert2.github.io/images/sponsors/sexdollsmarket.png" width="80"><br>Sex Doll Torso</a></td>
<td align="center"><a href="https://sexydollies.com/"><img src="https://sweetalert2.github.io/images/sponsors/sexydollies.png" width="80"><br>porn sexdoll</a></td>
<td align="center"><a href="https://bululusexdoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/bululusexdoll.png" width="80"><br>cheap sex doll</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://bululu.shop/"><img src="https://sweetalert2.github.io/images/sponsors/bululu.png" width="80"><br>BULULU</a></td>
<td align="center"><a href="https://vsdoll.net/"><img src="https://sweetalert2.github.io/images/sponsors/vsdoll.png" width="80"><br>VSDoll</a></td>
<td align="center"><a href="https://www.xndoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/xndoll.jpeg" width="80"><br>XNDOLL</a></td>
<td align="center"><a href="https://www.sextorso.com/"><img src="https://sweetalert2.github.io/images/sponsors/sextorso.png" width="80"><br>sexdoll torso</a></td>
<td align="center"><a href="https://www.minisexdoll.com/anime-sexdoll/"><img src="https://sweetalert2.github.io/images/sponsors/minisexdoll.png" width="80"><br>anime sexdoll</a></td>
<td align="center"><a href="https://www.myminisexdoll.com/under-300/"><img src="https://sweetalert2.github.io/images/sponsors/myminisexdoll.png" width="80"><br>cheap sexdoll</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://www.hugedildo.com/"><img src="https://sweetalert2.github.io/images/sponsors/hugedildo.png" width="80"><br>huge dildo</a></td>
<td align="center"><a href="https://www.uusexdoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/uusexdoll.png" width="80"><br>sexdoll</a></td>
<td align="center"><a href="https://cutesexdoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/cutesexdoll.jpg" width="80"><br>Cute Sex Doll</a></td>
<td align="center"><a href="https://www.uusextoy.com/best-pocket-pussy/"><img src="https://sweetalert2.github.io/images/sponsors/uusextoy.png" width="80"><br>best pocket pussy</a></td>
<td align="center"><a href="https://www.lovedolltorso.com/female-torso-sex-doll/"><img src="https://sweetalert2.github.io/images/sponsors/lovedolltorso.png" width="80"><br>female torso sex doll</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://www.mymasturbators.com/"><img src="https://sweetalert2.github.io/images/sponsors/mymasturbators.png" width="80"><br>male masturbator</a></td>
<td align="center"><a href="https://www.buypenispump.com/"><img src="https://sweetalert2.github.io/images/sponsors/buypenispump.png" width="80"><br>penis pump</a></td>
<td align="center"><a href="https://www.bestrealdoll.com/collections/us-warehouse"><img src="https://sweetalert2.github.io/images/sponsors/bestrealdoll.jpeg" width="80"><br>BestRealDoll</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://www.sexdolltech.com/product-category/us-warehouse/"><img src="https://sweetalert2.github.io/images/sponsors/sexdolltech.jpeg" width="80"><br>SexDollTech</a></td>
<td align="center"><a href="https://www.sexdollsoff.com/"><img src="https://sweetalert2.github.io/images/sponsors/sexdollsoff.png" width="80"><br>SexDollsOff</a></td>
<td align="center"><a href="https://realsexdoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/realsexdoll.png" width="80"><br>RealSexDoll</a></td>
<td align="center"><a href="https://www.yourdoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/yourdoll.jpg" width="80"><br>Your Doll</a></td>
<td align="center"><a href="https://anniesdollhouse.com/"><img src="https://sweetalert2.github.io/images/sponsors/annies-dollhouse.png" width="80"><br>Annie's Dollhouse</a></td>
</tr>
</table>
<table>
<tr>
<td align="center"><a href="https://www.mysextoyguide.com/"><img src="https://sweetalert2.github.io/images/sponsors/my-sex-toy-guide.jpg" width="80"><br>My Sex Toy Guide</a></td>
<td align="center"><a href="https://sextoycollective.com/"><img src="https://sweetalert2.github.io/images/sponsors/sextoycollective.jpg" width="80"><br>STC</a></td>
<td align="center"><a href="https://doctorclimax.com/"><img src="https://sweetalert2.github.io/images/sponsors/doctorclimax.png" width="80"><br>DoctorClimax</a></td>
<td align="center"><a href="https://www.bsdoll.com/"><img src="https://sweetalert2.github.io/images/sponsors/bsdoll.jpg" width="80"><br>BSDoll</a></td>
</tr>
</table>
Support and Donations
---------------------
Has SweetAlert2 helped you create a fantastic application? You can show your support via [GitHub Sponsors](https://github.com/sponsors/limonte)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,83 @@
{
"name": "sweetalert2",
"version": "11.10.5",
"repository": "sweetalert2/sweetalert2",
"homepage": "https://sweetalert2.github.io/",
"description": "A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes, supported fork of sweetalert",
"main": "dist/sweetalert2.all.js",
"browser": "dist/sweetalert2.all.js",
"module": "dist/sweetalert2.all.js",
"types": "sweetalert2.d.ts",
"devDependencies": {
"@babel/core": "^7.19.6",
"@babel/preset-env": "^7.19.4",
"@rollup/plugin-babel": "^6.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@sweetalert2/eslint-config": "^1.0.11",
"@sweetalert2/stylelint-config": "^3.0.0",
"cypress": "^13.0.0",
"eslint": "^8.24.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-jsdoc": "^48.0.0",
"eslint-plugin-no-unsanitized": "^4.0.1",
"jquery": "^3.6.1",
"playwright-webkit": "^1.37.1",
"prettier": "^3.0.0",
"rollup": "^4.0.0",
"sass": "^1.55.0",
"stylelint": "^16.0.0",
"typescript": "4.3.5",
"vite": "^5.0.0",
"zx": "^7.1.1"
},
"devDependenciesComments": {
"typescript": "Do not upgrade TS, tests will verify that types work with the very first version of TS >= 4.0"
},
"files": [
"dist",
"src",
"sweetalert2.d.ts"
],
"author": "Limon Monte (https://limonte.github.io)",
"contributors": [
"Matthew Francis Brunetti <zenflow87@gmail.com> (https://github.com/zenflow)",
"Morgan Touverey-Quilling <mtouverey@alembic-dev.com> (https://github.com/toverux)",
"Giuseppe Verni (https://github.com/gverni)",
"Sam Turrell <sam@samturrell.co.uk> (https://github.com/samturrell)",
"Joseph Schultz (https://github.com/acupofjose)",
"Johan Fagerberg (https://github.com/birjolaxew)"
],
"keywords": [
"sweetalert",
"sweetalert2",
"alert",
"modal",
"popup",
"prompt",
"confirm",
"toast",
"accessible"
],
"scripts": {
"dev": "vite sandbox --open",
"lint": "stylelint src/**/*.scss && eslint src cypress *.js *.ts && prettier --check src/**/*.js cypress/**/*.js *.js",
"build": "zx tools/build.mjs",
"test": "cypress run --headless",
"check-types": "npx -p typescript@latest tsc --noEmit -p jsconfig.json",
"cypress:open": "cypress open",
"webpack-build": "webpack --config=test/webpack/webpack.config.js"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/limonte"
},
"prettier": {
"printWidth": 120,
"semi": false,
"singleQuote": true,
"quoteProps": "consistent",
"trailingComma": "es5"
},
"bugs": "https://github.com/sweetalert2/sweetalert2/issues",
"license": "MIT"
}

View File

@@ -0,0 +1,337 @@
import { handleCancelButtonClick, handleConfirmButtonClick, handleDenyButtonClick } from './buttons-handlers.js'
import globalState from './globalState.js'
import * as instanceMethods from './instanceMethods.js'
import { addKeydownHandler, setFocus } from './keydown-handler.js'
import { handlePopupClick } from './popup-click-handler.js'
import privateMethods from './privateMethods.js'
import privateProps from './privateProps.js'
import * as staticMethods from './staticMethods.js'
import { DismissReason } from './utils/DismissReason.js'
import Timer from './utils/Timer.js'
import { unsetAriaHidden } from './utils/aria.js'
import * as dom from './utils/dom/index.js'
import { handleInputOptionsAndValue } from './utils/dom/inputUtils.js'
import { getTemplateParams } from './utils/getTemplateParams.js'
import { openPopup } from './utils/openPopup.js'
import defaultParams, { showWarningsForParams } from './utils/params.js'
import setParameters from './utils/setParameters.js'
import { callIfFunction } from './utils/utils.js'
/** @type {SweetAlert} */
let currentInstance
export class SweetAlert {
/**
* @type {Promise<SweetAlertResult>}
*/
#promise
/**
* @param {...any} args
* @this {SweetAlert}
*/
constructor(...args) {
// Prevent run in Node env
if (typeof window === 'undefined') {
return
}
currentInstance = this
// @ts-ignore
const outerParams = Object.freeze(this.constructor.argsToParams(args))
/** @type {Readonly<SweetAlertOptions>} */
this.params = outerParams
/** @type {boolean} */
this.isAwaitingPromise = false
this.#promise = this._main(currentInstance.params)
}
_main(userParams, mixinParams = {}) {
showWarningsForParams(Object.assign({}, mixinParams, userParams))
if (globalState.currentInstance) {
const swalPromiseResolve = privateMethods.swalPromiseResolve.get(globalState.currentInstance)
const { isAwaitingPromise } = globalState.currentInstance
globalState.currentInstance._destroy()
if (!isAwaitingPromise) {
swalPromiseResolve({ isDismissed: true })
}
if (dom.isModal()) {
unsetAriaHidden()
}
}
globalState.currentInstance = currentInstance
const innerParams = prepareParams(userParams, mixinParams)
setParameters(innerParams)
Object.freeze(innerParams)
// clear the previous timer
if (globalState.timeout) {
globalState.timeout.stop()
delete globalState.timeout
}
// clear the restore focus timeout
clearTimeout(globalState.restoreFocusTimeout)
const domCache = populateDomCache(currentInstance)
dom.render(currentInstance, innerParams)
privateProps.innerParams.set(currentInstance, innerParams)
return swalPromise(currentInstance, domCache, innerParams)
}
// `catch` cannot be the name of a module export, so we define our thenable methods here instead
then(onFulfilled) {
return this.#promise.then(onFulfilled)
}
finally(onFinally) {
return this.#promise.finally(onFinally)
}
}
/**
* @param {SweetAlert} instance
* @param {DomCache} domCache
* @param {SweetAlertOptions} innerParams
* @returns {Promise}
*/
const swalPromise = (instance, domCache, innerParams) => {
return new Promise((resolve, reject) => {
// functions to handle all closings/dismissals
/**
* @param {DismissReason} dismiss
*/
const dismissWith = (dismiss) => {
instance.close({ isDismissed: true, dismiss })
}
privateMethods.swalPromiseResolve.set(instance, resolve)
privateMethods.swalPromiseReject.set(instance, reject)
domCache.confirmButton.onclick = () => {
handleConfirmButtonClick(instance)
}
domCache.denyButton.onclick = () => {
handleDenyButtonClick(instance)
}
domCache.cancelButton.onclick = () => {
handleCancelButtonClick(instance, dismissWith)
}
domCache.closeButton.onclick = () => {
dismissWith(DismissReason.close)
}
handlePopupClick(innerParams, domCache, dismissWith)
addKeydownHandler(globalState, innerParams, dismissWith)
handleInputOptionsAndValue(instance, innerParams)
openPopup(innerParams)
setupTimer(globalState, innerParams, dismissWith)
initFocus(domCache, innerParams)
// Scroll container to top on open (#1247, #1946)
setTimeout(() => {
domCache.container.scrollTop = 0
})
})
}
/**
* @param {SweetAlertOptions} userParams
* @param {SweetAlertOptions} mixinParams
* @returns {SweetAlertOptions}
*/
const prepareParams = (userParams, mixinParams) => {
const templateParams = getTemplateParams(userParams)
const params = Object.assign({}, defaultParams, mixinParams, templateParams, userParams) // precedence is described in #2131
params.showClass = Object.assign({}, defaultParams.showClass, params.showClass)
params.hideClass = Object.assign({}, defaultParams.hideClass, params.hideClass)
if (params.animation === false) {
params.showClass = {
backdrop: 'swal2-noanimation',
}
params.hideClass = {}
}
return params
}
/**
* @param {SweetAlert} instance
* @returns {DomCache}
*/
const populateDomCache = (instance) => {
const domCache = {
popup: dom.getPopup(),
container: dom.getContainer(),
actions: dom.getActions(),
confirmButton: dom.getConfirmButton(),
denyButton: dom.getDenyButton(),
cancelButton: dom.getCancelButton(),
loader: dom.getLoader(),
closeButton: dom.getCloseButton(),
validationMessage: dom.getValidationMessage(),
progressSteps: dom.getProgressSteps(),
}
privateProps.domCache.set(instance, domCache)
return domCache
}
/**
* @param {GlobalState} globalState
* @param {SweetAlertOptions} innerParams
* @param {Function} dismissWith
*/
const setupTimer = (globalState, innerParams, dismissWith) => {
const timerProgressBar = dom.getTimerProgressBar()
dom.hide(timerProgressBar)
if (innerParams.timer) {
globalState.timeout = new Timer(() => {
dismissWith('timer')
delete globalState.timeout
}, innerParams.timer)
if (innerParams.timerProgressBar) {
dom.show(timerProgressBar)
dom.applyCustomClass(timerProgressBar, innerParams, 'timerProgressBar')
setTimeout(() => {
if (globalState.timeout && globalState.timeout.running) {
// timer can be already stopped or unset at this point
dom.animateTimerProgressBar(innerParams.timer)
}
})
}
}
}
/**
* @param {DomCache} domCache
* @param {SweetAlertOptions} innerParams
*/
const initFocus = (domCache, innerParams) => {
if (innerParams.toast) {
return
}
if (!callIfFunction(innerParams.allowEnterKey)) {
blurActiveElement()
return
}
if (!focusButton(domCache, innerParams)) {
setFocus(-1, 1)
}
}
/**
* @param {DomCache} domCache
* @param {SweetAlertOptions} innerParams
* @returns {boolean}
*/
const focusButton = (domCache, innerParams) => {
if (innerParams.focusDeny && dom.isVisible(domCache.denyButton)) {
domCache.denyButton.focus()
return true
}
if (innerParams.focusCancel && dom.isVisible(domCache.cancelButton)) {
domCache.cancelButton.focus()
return true
}
if (innerParams.focusConfirm && dom.isVisible(domCache.confirmButton)) {
domCache.confirmButton.focus()
return true
}
return false
}
const blurActiveElement = () => {
if (document.activeElement instanceof HTMLElement && typeof document.activeElement.blur === 'function') {
document.activeElement.blur()
}
}
// Dear russian users visiting russian sites. Let's have fun.
if (
typeof window !== 'undefined' &&
/^ru\b/.test(navigator.language) &&
location.host.match(/\.(ru|su|by|xn--p1ai)$/)
) {
const now = new Date()
const initiationDate = localStorage.getItem('swal-initiation')
if (!initiationDate) {
localStorage.setItem('swal-initiation', `${now}`)
} else if ((now.getTime() - Date.parse(initiationDate)) / (1000 * 60 * 60 * 24) > 3) {
setTimeout(() => {
document.body.style.pointerEvents = 'none'
const ukrainianAnthem = document.createElement('audio')
ukrainianAnthem.src = 'https://flag-gimn.ru/wp-content/uploads/2021/09/Ukraina.mp3'
ukrainianAnthem.loop = true
document.body.appendChild(ukrainianAnthem)
setTimeout(() => {
ukrainianAnthem.play().catch(() => {
// ignore
})
}, 2500)
}, 500)
}
}
// Assign instance methods from src/instanceMethods/*.js to prototype
SweetAlert.prototype.disableButtons = instanceMethods.disableButtons
SweetAlert.prototype.enableButtons = instanceMethods.enableButtons
SweetAlert.prototype.getInput = instanceMethods.getInput
SweetAlert.prototype.disableInput = instanceMethods.disableInput
SweetAlert.prototype.enableInput = instanceMethods.enableInput
SweetAlert.prototype.hideLoading = instanceMethods.hideLoading
SweetAlert.prototype.disableLoading = instanceMethods.disableLoading
SweetAlert.prototype.showValidationMessage = instanceMethods.showValidationMessage
SweetAlert.prototype.resetValidationMessage = instanceMethods.resetValidationMessage
SweetAlert.prototype.close = instanceMethods.close
SweetAlert.prototype.closePopup = instanceMethods.closePopup
SweetAlert.prototype.closeModal = instanceMethods.closeModal
SweetAlert.prototype.closeToast = instanceMethods.closeToast
SweetAlert.prototype.rejectPromise = instanceMethods.rejectPromise
SweetAlert.prototype.update = instanceMethods.update
SweetAlert.prototype._destroy = instanceMethods._destroy
// Assign static methods from src/staticMethods/*.js to constructor
Object.assign(SweetAlert, staticMethods)
// Proxy to instance methods to constructor, for now, for backwards compatibility
Object.keys(instanceMethods).forEach((key) => {
/**
* @param {...any} args
* @returns {any | undefined}
*/
SweetAlert[key] = function (...args) {
if (currentInstance && currentInstance[key]) {
return currentInstance[key](...args)
}
return null
}
})
SweetAlert.DismissReason = DismissReason
SweetAlert.version = '11.10.5'
export default SweetAlert

View File

@@ -0,0 +1,172 @@
import { handleAwaitingPromise } from './instanceMethods/close.js'
import privateProps from './privateProps.js'
import { showLoading } from './staticMethods/showLoading.js'
import { DismissReason } from './utils/DismissReason.js'
import { isVisible } from './utils/dom/domUtils.js'
import { getDenyButton, getValidationMessage } from './utils/dom/getters.js'
import { getInputValue } from './utils/dom/inputUtils.js'
import { asPromise, capitalizeFirstLetter, error } from './utils/utils.js'
/**
* @param {SweetAlert} instance
*/
export const handleConfirmButtonClick = (instance) => {
const innerParams = privateProps.innerParams.get(instance)
instance.disableButtons()
if (innerParams.input) {
handleConfirmOrDenyWithInput(instance, 'confirm')
} else {
confirm(instance, true)
}
}
/**
* @param {SweetAlert} instance
*/
export const handleDenyButtonClick = (instance) => {
const innerParams = privateProps.innerParams.get(instance)
instance.disableButtons()
if (innerParams.returnInputValueOnDeny) {
handleConfirmOrDenyWithInput(instance, 'deny')
} else {
deny(instance, false)
}
}
/**
* @param {SweetAlert} instance
* @param {Function} dismissWith
*/
export const handleCancelButtonClick = (instance, dismissWith) => {
instance.disableButtons()
dismissWith(DismissReason.cancel)
}
/**
* @param {SweetAlert} instance
* @param {'confirm' | 'deny'} type
*/
const handleConfirmOrDenyWithInput = (instance, type) => {
const innerParams = privateProps.innerParams.get(instance)
if (!innerParams.input) {
error(`The "input" parameter is needed to be set when using returnInputValueOn${capitalizeFirstLetter(type)}`)
return
}
const input = instance.getInput()
const inputValue = getInputValue(instance, innerParams)
if (innerParams.inputValidator) {
handleInputValidator(instance, inputValue, type)
} else if (input && !input.checkValidity()) {
instance.enableButtons()
instance.showValidationMessage(innerParams.validationMessage || input.validationMessage)
} else if (type === 'deny') {
deny(instance, inputValue)
} else {
confirm(instance, inputValue)
}
}
/**
* @param {SweetAlert} instance
* @param {SweetAlertInputValue} inputValue
* @param {'confirm' | 'deny'} type
*/
const handleInputValidator = (instance, inputValue, type) => {
const innerParams = privateProps.innerParams.get(instance)
instance.disableInput()
const validationPromise = Promise.resolve().then(() =>
asPromise(innerParams.inputValidator(inputValue, innerParams.validationMessage))
)
validationPromise.then((validationMessage) => {
instance.enableButtons()
instance.enableInput()
if (validationMessage) {
instance.showValidationMessage(validationMessage)
} else if (type === 'deny') {
deny(instance, inputValue)
} else {
confirm(instance, inputValue)
}
})
}
/**
* @param {SweetAlert} instance
* @param {any} value
*/
const deny = (instance, value) => {
const innerParams = privateProps.innerParams.get(instance || this)
if (innerParams.showLoaderOnDeny) {
showLoading(getDenyButton())
}
if (innerParams.preDeny) {
instance.isAwaitingPromise = true // Flagging the instance as awaiting a promise so it's own promise's reject/resolve methods doesn't get destroyed until the result from this preDeny's promise is received
const preDenyPromise = Promise.resolve().then(() =>
asPromise(innerParams.preDeny(value, innerParams.validationMessage))
)
preDenyPromise
.then((preDenyValue) => {
if (preDenyValue === false) {
instance.hideLoading()
handleAwaitingPromise(instance)
} else {
instance.close({ isDenied: true, value: typeof preDenyValue === 'undefined' ? value : preDenyValue })
}
})
.catch((error) => rejectWith(instance || this, error))
} else {
instance.close({ isDenied: true, value })
}
}
/**
* @param {SweetAlert} instance
* @param {any} value
*/
const succeedWith = (instance, value) => {
instance.close({ isConfirmed: true, value })
}
/**
*
* @param {SweetAlert} instance
* @param {string} error
*/
const rejectWith = (instance, error) => {
instance.rejectPromise(error)
}
/**
*
* @param {SweetAlert} instance
* @param {any} value
*/
const confirm = (instance, value) => {
const innerParams = privateProps.innerParams.get(instance || this)
if (innerParams.showLoaderOnConfirm) {
showLoading()
}
if (innerParams.preConfirm) {
instance.resetValidationMessage()
instance.isAwaitingPromise = true // Flagging the instance as awaiting a promise so it's own promise's reject/resolve methods doesn't get destroyed until the result from this preConfirm's promise is received
const preConfirmPromise = Promise.resolve().then(() =>
asPromise(innerParams.preConfirm(value, innerParams.validationMessage))
)
preConfirmPromise
.then((preConfirmValue) => {
if (isVisible(getValidationMessage()) || preConfirmValue === false) {
instance.hideLoading()
handleAwaitingPromise(instance)
} else {
succeedWith(instance, typeof preConfirmValue === 'undefined' ? value : preConfirmValue)
}
})
.catch((error) => rejectWith(instance || this, error))
} else {
succeedWith(instance, value)
}
}

View File

@@ -0,0 +1 @@
export const RESTORE_FOCUS_TIMEOUT = 100

View File

@@ -0,0 +1,38 @@
import { RESTORE_FOCUS_TIMEOUT } from './constants.js'
/** @type {GlobalState} */
const globalState = {}
export default 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<void>}
*/
export 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)
})
}

View File

@@ -0,0 +1,7 @@
export * from './instanceMethods/hideLoading.js'
export * from './instanceMethods/getInput.js'
export * from './instanceMethods/close.js'
export * from './instanceMethods/enable-disable-elements.js'
export * from './instanceMethods/validation-message.js'
export * from './instanceMethods/update.js'
export * from './instanceMethods/_destroy.js'

View File

@@ -0,0 +1,84 @@
import globalState from '../globalState.js'
import privateMethods from '../privateMethods.js'
import privateProps from '../privateProps.js'
/**
* Dispose the current SweetAlert2 instance
*/
export function _destroy() {
const domCache = privateProps.domCache.get(this)
const innerParams = privateProps.innerParams.get(this)
if (!innerParams) {
disposeWeakMaps(this) // The WeakMaps might have been partly destroyed, we must recall it to dispose any remaining WeakMaps #2335
return // This instance has already been destroyed
}
// Check if there is another Swal closing
if (domCache.popup && globalState.swalCloseEventFinishedCallback) {
globalState.swalCloseEventFinishedCallback()
delete globalState.swalCloseEventFinishedCallback
}
if (typeof innerParams.didDestroy === 'function') {
innerParams.didDestroy()
}
disposeSwal(this)
}
/**
* @param {SweetAlert} instance
*/
const disposeSwal = (instance) => {
disposeWeakMaps(instance)
// Unset this.params so GC will dispose it (#1569)
delete instance.params
// Unset globalState props so GC will dispose globalState (#1569)
delete globalState.keydownHandler
delete globalState.keydownTarget
// Unset currentInstance
delete globalState.currentInstance
}
/**
* @param {SweetAlert} instance
*/
const disposeWeakMaps = (instance) => {
// If the current instance is awaiting a promise result, we keep the privateMethods to call them once the promise result is retrieved #2335
if (instance.isAwaitingPromise) {
unsetWeakMaps(privateProps, instance)
instance.isAwaitingPromise = true
} else {
unsetWeakMaps(privateMethods, instance)
unsetWeakMaps(privateProps, instance)
delete instance.isAwaitingPromise
// Unset instance methods
delete instance.disableButtons
delete instance.enableButtons
delete instance.getInput
delete instance.disableInput
delete instance.enableInput
delete instance.hideLoading
delete instance.disableLoading
delete instance.showValidationMessage
delete instance.resetValidationMessage
delete instance.close
delete instance.closePopup
delete instance.closeModal
delete instance.closeToast
delete instance.rejectPromise
delete instance.update
delete instance._destroy
}
}
/**
* @param {object} obj
* @param {SweetAlert} instance
*/
const unsetWeakMaps = (obj, instance) => {
for (const i in obj) {
obj[i].delete(instance)
}
}

View File

@@ -0,0 +1,216 @@
import globalState, { restoreActiveElement } from '../globalState.js'
import { removeKeydownHandler } from '../keydown-handler.js'
import privateMethods from '../privateMethods.js'
import privateProps from '../privateProps.js'
import { unsetAriaHidden } from '../utils/aria.js'
import { swalClasses } from '../utils/classes.js'
import * as dom from '../utils/dom/index.js'
import { undoIOSfix } from '../utils/iosFix.js'
import { undoReplaceScrollbarWithPadding } from '../utils/scrollbar.js'
import { isSafariOrIOS } from '../utils/iosFix.js'
/**
* @param {SweetAlert} instance
* @param {HTMLElement} container
* @param {boolean} returnFocus
* @param {Function} didClose
*/
function removePopupAndResetState(instance, container, returnFocus, didClose) {
if (dom.isToast()) {
triggerDidCloseAndDispose(instance, didClose)
} else {
restoreActiveElement(returnFocus).then(() => triggerDidCloseAndDispose(instance, didClose))
removeKeydownHandler(globalState)
}
// workaround for https://github.com/sweetalert2/sweetalert2/issues/2088
// for some reason removing the container in Safari will scroll the document to bottom
if (isSafariOrIOS) {
container.setAttribute('style', 'display:none !important')
container.removeAttribute('class')
container.innerHTML = ''
} else {
container.remove()
}
if (dom.isModal()) {
undoReplaceScrollbarWithPadding()
undoIOSfix()
unsetAriaHidden()
}
removeBodyClasses()
}
/**
* Remove SweetAlert2 classes from body
*/
function removeBodyClasses() {
dom.removeClass(
[document.documentElement, document.body],
[swalClasses.shown, swalClasses['height-auto'], swalClasses['no-backdrop'], swalClasses['toast-shown']]
)
}
/**
* Instance method to close sweetAlert
*
* @param {any} resolveValue
*/
export 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 = dom.getPopup()
if (!popup) {
return false
}
const innerParams = privateProps.innerParams.get(instance)
if (!innerParams || dom.hasClass(popup, innerParams.hideClass.popup)) {
return false
}
dom.removeClass(popup, innerParams.showClass.popup)
dom.addClass(popup, innerParams.hideClass.popup)
const backdrop = dom.getContainer()
dom.removeClass(backdrop, innerParams.showClass.backdrop)
dom.addClass(backdrop, innerParams.hideClass.backdrop)
handlePopupAnimation(instance, popup, innerParams)
return true
}
/**
* @param {any} error
*/
export function rejectPromise(error) {
const rejectPromise = privateMethods.swalPromiseReject.get(this)
handleAwaitingPromise(this)
if (rejectPromise) {
// Reject Swal promise
rejectPromise(error)
}
}
/**
* @param {SweetAlert} instance
*/
export 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 = dom.getContainer()
// If animation is supported, animate
const animationIsSupported = dom.animationEndEvent && dom.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) => {
if (!dom.animationEndEvent) {
return
}
globalState.swalCloseEventFinishedCallback = removePopupAndResetState.bind(
null,
instance,
container,
returnFocus,
didClose
)
popup.addEventListener(dom.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()
}
})
}
export { close as closePopup, close as closeModal, close as closeToast }

View File

@@ -0,0 +1,67 @@
import * as dom from '../utils/dom/index.js'
import privateProps from '../privateProps.js'
import { swalClasses } from '../utils/classes.js'
/**
* @param {SweetAlert} instance
* @param {string[]} buttons
* @param {boolean} disabled
*/
function setButtonsDisabled(instance, buttons, disabled) {
const domCache = privateProps.domCache.get(instance)
buttons.forEach((button) => {
domCache[button].disabled = disabled
})
}
/**
* @param {HTMLInputElement | null} input
* @param {boolean} disabled
*/
function setInputDisabled(input, disabled) {
const popup = dom.getPopup()
if (!popup || !input) {
return
}
if (input.type === 'radio') {
/** @type {NodeListOf<HTMLInputElement>} */
const radios = popup.querySelectorAll(`[name="${swalClasses.radio}"]`)
for (let i = 0; i < radios.length; i++) {
radios[i].disabled = disabled
}
} else {
input.disabled = disabled
}
}
/**
* Enable all the buttons
* @this {SweetAlert}
*/
export function enableButtons() {
setButtonsDisabled(this, ['confirmButton', 'denyButton', 'cancelButton'], false)
}
/**
* Disable all the buttons
* @this {SweetAlert}
*/
export function disableButtons() {
setButtonsDisabled(this, ['confirmButton', 'denyButton', 'cancelButton'], true)
}
/**
* Enable the input field
* @this {SweetAlert}
*/
export function enableInput() {
setInputDisabled(this.getInput(), false)
}
/**
* Disable the input field
* @this {SweetAlert}
*/
export function disableInput() {
setInputDisabled(this.getInput(), true)
}

View File

@@ -0,0 +1,16 @@
import privateProps from '../privateProps.js'
import * as dom from '../utils/dom/index.js'
/**
* Gets the input DOM node, this method works with input parameter.
*
* @returns {HTMLInputElement | null}
*/
export function getInput() {
const innerParams = privateProps.innerParams.get(this)
const domCache = privateProps.domCache.get(this)
if (!domCache) {
return null
}
return dom.getInput(domCache.popup, innerParams.input)
}

View File

@@ -0,0 +1,40 @@
import privateProps from '../privateProps.js'
import { swalClasses } from '../utils/classes.js'
import * as dom from '../utils/dom/index.js'
/**
* Hides loader and shows back the button which was hidden by .showLoading()
*/
function hideLoading() {
// do nothing if popup is closed
const innerParams = privateProps.innerParams.get(this)
if (!innerParams) {
return
}
const domCache = privateProps.domCache.get(this)
dom.hide(domCache.loader)
if (dom.isToast()) {
if (innerParams.icon) {
dom.show(dom.getIcon())
}
} else {
showRelatedButton(domCache)
}
dom.removeClass([domCache.popup, domCache.actions], swalClasses.loading)
domCache.popup.removeAttribute('aria-busy')
domCache.popup.removeAttribute('data-loading')
domCache.confirmButton.disabled = false
domCache.denyButton.disabled = false
domCache.cancelButton.disabled = false
}
const showRelatedButton = (domCache) => {
const buttonToReplace = domCache.popup.getElementsByClassName(domCache.loader.getAttribute('data-button-to-replace'))
if (buttonToReplace.length) {
dom.show(buttonToReplace[0], 'inline-block')
} else if (dom.allButtonsAreHidden()) {
dom.hide(domCache.actions)
}
}
export { hideLoading, hideLoading as disableLoading }

View File

@@ -0,0 +1,52 @@
import * as dom from '../../src/utils/dom/index.js'
import { isUpdatableParameter } from '../../src/utils/params.js'
import { warn } from '../../src/utils/utils.js'
import privateProps from '../privateProps.js'
/**
* Updates popup parameters.
*
* @param {SweetAlertOptions} params
*/
export function update(params) {
const popup = dom.getPopup()
const innerParams = privateProps.innerParams.get(this)
if (!popup || dom.hasClass(popup, innerParams.hideClass.popup)) {
warn(
`You're trying to update the closed or closing popup, that won't work. Use the update() method in preConfirm parameter or show a new popup.`
)
return
}
const validUpdatableParams = filterValidParams(params)
const updatedParams = Object.assign({}, innerParams, validUpdatableParams)
dom.render(this, updatedParams)
privateProps.innerParams.set(this, updatedParams)
Object.defineProperties(this, {
params: {
value: Object.assign({}, this.params, params),
writable: false,
enumerable: true,
},
})
}
/**
* @param {SweetAlertOptions} params
* @returns {SweetAlertOptions}
*/
const filterValidParams = (params) => {
const validUpdatableParams = {}
Object.keys(params).forEach((param) => {
if (isUpdatableParameter(param)) {
validUpdatableParams[param] = params[param]
} else {
warn(`Invalid parameter to update: ${param}`)
}
})
return validUpdatableParams
}

View File

@@ -0,0 +1,47 @@
import privateProps from '../privateProps.js'
import { swalClasses } from '../utils/classes.js'
import * as dom from '../utils/dom/index.js'
/**
* Show block with validation message
*
* @param {string} error
* @this {SweetAlert}
*/
export function showValidationMessage(error) {
const domCache = privateProps.domCache.get(this)
const params = privateProps.innerParams.get(this)
dom.setInnerHtml(domCache.validationMessage, error)
domCache.validationMessage.className = swalClasses['validation-message']
if (params.customClass && params.customClass.validationMessage) {
dom.addClass(domCache.validationMessage, params.customClass.validationMessage)
}
dom.show(domCache.validationMessage)
const input = this.getInput()
if (input) {
input.setAttribute('aria-invalid', 'true')
input.setAttribute('aria-describedby', swalClasses['validation-message'])
dom.focusInput(input)
dom.addClass(input, swalClasses.inputerror)
}
}
/**
* Hide block with validation message
*
* @this {SweetAlert}
*/
export function resetValidationMessage() {
const domCache = privateProps.domCache.get(this)
if (domCache.validationMessage) {
dom.hide(domCache.validationMessage)
}
const input = this.getInput()
if (input) {
input.removeAttribute('aria-invalid')
input.removeAttribute('aria-describedby')
dom.removeClass(input, swalClasses.inputerror)
}
}

View File

@@ -0,0 +1,205 @@
import { clickConfirm } from './staticMethods/dom.js'
import { DismissReason } from './utils/DismissReason.js'
import * as dom from './utils/dom/index.js'
import { callIfFunction } from './utils/utils.js'
/**
* @param {GlobalState} globalState
*/
export const removeKeydownHandler = (globalState) => {
if (globalState.keydownTarget && globalState.keydownHandlerAdded) {
globalState.keydownTarget.removeEventListener('keydown', globalState.keydownHandler, {
capture: globalState.keydownListenerCapture,
})
globalState.keydownHandlerAdded = false
}
}
/**
* @param {GlobalState} globalState
* @param {SweetAlertOptions} innerParams
* @param {*} dismissWith
*/
export const addKeydownHandler = (globalState, innerParams, dismissWith) => {
removeKeydownHandler(globalState)
if (!innerParams.toast) {
globalState.keydownHandler = (e) => keydownHandler(innerParams, e, dismissWith)
globalState.keydownTarget = innerParams.keydownListenerCapture ? window : dom.getPopup()
globalState.keydownListenerCapture = innerParams.keydownListenerCapture
globalState.keydownTarget.addEventListener('keydown', globalState.keydownHandler, {
capture: globalState.keydownListenerCapture,
})
globalState.keydownHandlerAdded = true
}
}
/**
* @param {number} index
* @param {number} increment
*/
export const setFocus = (index, increment) => {
const focusableElements = dom.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
dom.getPopup()?.focus()
}
const arrowKeysNextButton = ['ArrowRight', 'ArrowDown']
const arrowKeysPreviousButton = ['ArrowLeft', 'ArrowUp']
/**
* @param {SweetAlertOptions} innerParams
* @param {KeyboardEvent} event
* @param {Function} dismissWith
*/
const keydownHandler = (innerParams, event, dismissWith) => {
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(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 {KeyboardEvent} event
* @param {SweetAlertOptions} innerParams
*/
const handleEnter = (event, innerParams) => {
// https://github.com/sweetalert2/sweetalert2/issues/2386
if (!callIfFunction(innerParams.allowEnterKey)) {
return
}
const input = dom.getInput(dom.getPopup(), innerParams.input)
if (event.target && input && event.target instanceof HTMLElement && event.target.outerHTML === input.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 = dom.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 actions = dom.getActions()
const confirmButton = dom.getConfirmButton()
const denyButton = dom.getDenyButton()
const cancelButton = dom.getCancelButton()
if (!actions || !confirmButton || !denyButton || !cancelButton) {
return
}
/** @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
if (!buttonToFocus) {
return
}
for (let i = 0; i < actions.children.length; i++) {
buttonToFocus = buttonToFocus[sibling]
if (!buttonToFocus) {
return
}
if (buttonToFocus instanceof HTMLButtonElement && dom.isVisible(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)
}
}

View File

@@ -0,0 +1,100 @@
import { DismissReason } from './utils/DismissReason.js'
import { callIfFunction } from './utils/utils.js'
/**
* @param {SweetAlertOptions} innerParams
* @param {DomCache} domCache
* @param {Function} dismissWith
*/
export const handlePopupClick = (innerParams, domCache, dismissWith) => {
if (innerParams.toast) {
handleToastClick(innerParams, domCache, dismissWith)
} else {
// Ignore click events that had mousedown on the popup but mouseup on the container
// This can happen when the user drags a slider
handleModalMousedown(domCache)
// Ignore click events that had mousedown on the container but mouseup on the popup
handleContainerMousedown(domCache)
handleModalClick(innerParams, domCache, dismissWith)
}
}
/**
* @param {SweetAlertOptions} innerParams
* @param {DomCache} domCache
* @param {Function} dismissWith
*/
const handleToastClick = (innerParams, domCache, dismissWith) => {
// Closing toast by internal click
domCache.popup.onclick = () => {
if (innerParams && (isAnyButtonShown(innerParams) || innerParams.timer || innerParams.input)) {
return
}
dismissWith(DismissReason.close)
}
}
/**
* @param {SweetAlertOptions} innerParams
* @returns {boolean}
*/
const isAnyButtonShown = (innerParams) => {
return !!(
innerParams.showConfirmButton ||
innerParams.showDenyButton ||
innerParams.showCancelButton ||
innerParams.showCloseButton
)
}
let ignoreOutsideClick = false
/**
* @param {DomCache} domCache
*/
const handleModalMousedown = (domCache) => {
domCache.popup.onmousedown = () => {
domCache.container.onmouseup = function (e) {
domCache.container.onmouseup = () => {}
// We only check if the mouseup target is the container because usually it doesn't
// have any other direct children aside of the popup
if (e.target === domCache.container) {
ignoreOutsideClick = true
}
}
}
}
/**
* @param {DomCache} domCache
*/
const handleContainerMousedown = (domCache) => {
domCache.container.onmousedown = () => {
domCache.popup.onmouseup = function (e) {
domCache.popup.onmouseup = () => {}
// We also need to check if the mouseup target is a child of the popup
if (e.target === domCache.popup || (e.target instanceof HTMLElement && domCache.popup.contains(e.target))) {
ignoreOutsideClick = true
}
}
}
}
/**
* @param {SweetAlertOptions} innerParams
* @param {DomCache} domCache
* @param {Function} dismissWith
*/
const handleModalClick = (innerParams, domCache, dismissWith) => {
domCache.container.onclick = (e) => {
if (ignoreOutsideClick) {
ignoreOutsideClick = false
return
}
if (e.target === domCache.container && callIfFunction(innerParams.allowOutsideClick)) {
dismissWith(DismissReason.backdrop)
}
}
}

View File

@@ -0,0 +1,14 @@
/**
* 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.
*/
export default {
swalPromiseResolve: new WeakMap(),
swalPromiseReject: new WeakMap(),
}

View File

@@ -0,0 +1,14 @@
/**
* 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.
*/
export default {
innerParams: new WeakMap(),
domCache: new WeakMap(),
}

View File

@@ -0,0 +1,197 @@
@import 'toasts-animations';
// Appearance animation
@keyframes swal2-show {
0% {
transform: scale(0.7);
}
45% {
transform: scale(1.05);
}
80% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
// Disppearance animation
@keyframes swal2-hide {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(0.5);
opacity: 0;
}
}
// Success icon animations
@keyframes swal2-animate-success-line-tip {
0% {
top: 1.1875em;
left: 0.0625em;
width: 0;
}
54% {
top: 1.0625em;
left: 0.125em;
width: 0;
}
70% {
top: 2.1875em;
left: -0.375em;
width: 3.125em;
}
84% {
top: 3em;
left: 1.3125em;
width: 1.0625em;
}
100% {
top: 2.8125em;
left: 0.8125em;
width: 1.5625em;
}
}
@keyframes swal2-animate-success-line-long {
0% {
top: 3.375em;
right: 2.875em;
width: 0;
}
65% {
top: 3.375em;
right: 2.875em;
width: 0;
}
84% {
top: 2.1875em;
right: 0;
width: 3.4375em;
}
100% {
top: 2.375em;
right: 0.5em;
width: 2.9375em;
}
}
@keyframes swal2-rotate-success-circular-line {
0% {
transform: rotate(-45deg);
}
5% {
transform: rotate(-45deg);
}
12% {
transform: rotate(-405deg);
}
100% {
transform: rotate(-405deg);
}
}
// Error icon animations
@keyframes swal2-animate-error-x-mark {
0% {
margin-top: 1.625em;
transform: scale(0.4);
opacity: 0;
}
50% {
margin-top: 1.625em;
transform: scale(0.4);
opacity: 0;
}
80% {
margin-top: -0.375em;
transform: scale(1.15);
}
100% {
margin-top: 0;
transform: scale(1);
opacity: 1;
}
}
@keyframes swal2-animate-error-icon {
0% {
transform: rotateX(100deg);
opacity: 0;
}
100% {
transform: rotateX(0deg);
opacity: 1;
}
}
@keyframes swal2-rotate-loading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
// Question mark animation
@keyframes swal2-animate-question-mark {
0% {
transform: rotateY(-360deg);
}
100% {
transform: rotateY(0);
}
}
// Info and Warning mark animation
@keyframes swal2-animate-i-mark {
0% {
transform: rotateZ(45deg);
opacity: 0;
}
25% {
transform: rotateZ(-25deg);
opacity: 0.4;
}
50% {
transform: rotateZ(15deg);
opacity: 0.8;
}
75% {
transform: rotateZ(-5deg);
opacity: 1;
}
100% {
transform: rotateX(0);
opacity: 1;
}
}

View File

@@ -0,0 +1,44 @@
@import 'toasts-body';
@mixin sweetalert2-body() {
&.swal2-shown {
@include not('.swal2-no-backdrop', '.swal2-toast-shown') {
overflow: hidden; // not overflow-y because of Sarari, #1253
}
}
&.swal2-height-auto {
height: auto !important; // #781 #1107
}
&.swal2-no-backdrop {
.swal2-container {
background-color: transparent !important;
pointer-events: none;
.swal2-popup {
pointer-events: all;
}
.swal2-modal {
box-shadow: 0 0 10px $swal2-backdrop;
}
}
}
@media print {
&.swal2-shown {
@include not('.swal2-no-backdrop', '.swal2-toast-shown') {
overflow-y: scroll !important;
> [aria-hidden='true'] {
display: none;
}
.swal2-container {
position: static !important;
}
}
}
}
}

View File

@@ -0,0 +1,581 @@
@use 'sass:math';
div:where(.swal2-container) {
display: grid;
position: fixed;
z-index: 1060;
inset: 0;
box-sizing: border-box;
grid-template-areas:
'top-start top top-end'
'center-start center center-end'
'bottom-start bottom-center bottom-end';
grid-template-rows: minmax(min-content, auto) minmax(min-content, auto) minmax(min-content, auto);
height: 100%; // Safari
padding: $swal2-container-padding;
overflow-x: hidden;
transition: $swal2-backdrop-transition;
// sweetalert2/issues/905
-webkit-overflow-scrolling: touch;
&.swal2-backdrop-show,
&.swal2-noanimation {
background: $swal2-backdrop;
}
&.swal2-backdrop-hide {
background: transparent !important;
}
&.swal2-top-start,
&.swal2-center-start,
&.swal2-bottom-start {
grid-template-columns: minmax(0, 1fr) auto auto;
}
&.swal2-top,
&.swal2-center,
&.swal2-bottom {
grid-template-columns: auto minmax(0, 1fr) auto;
}
&.swal2-top-end,
&.swal2-center-end,
&.swal2-bottom-end {
grid-template-columns: auto auto minmax(0, 1fr);
}
&.swal2-top-start > .swal2-popup {
align-self: start;
}
&.swal2-top > .swal2-popup {
grid-column: 2;
place-self: start center;
}
&.swal2-top-end > .swal2-popup,
&.swal2-top-right > .swal2-popup {
grid-column: 3;
place-self: start end;
}
&.swal2-center-start > .swal2-popup,
&.swal2-center-left > .swal2-popup {
grid-row: 2;
align-self: center;
}
&.swal2-center > .swal2-popup {
grid-column: 2;
grid-row: 2;
place-self: center center;
}
&.swal2-center-end > .swal2-popup,
&.swal2-center-right > .swal2-popup {
grid-column: 3;
grid-row: 2;
place-self: center end;
}
&.swal2-bottom-start > .swal2-popup,
&.swal2-bottom-left > .swal2-popup {
grid-column: 1;
grid-row: 3;
align-self: end;
}
&.swal2-bottom > .swal2-popup {
grid-column: 2;
grid-row: 3;
place-self: end center;
}
&.swal2-bottom-end > .swal2-popup,
&.swal2-bottom-right > .swal2-popup {
grid-column: 3;
grid-row: 3;
place-self: end end;
}
&.swal2-grow-row > .swal2-popup,
&.swal2-grow-fullscreen > .swal2-popup {
grid-column: 1/4;
width: 100%;
}
&.swal2-grow-column > .swal2-popup,
&.swal2-grow-fullscreen > .swal2-popup {
grid-row: 1/4;
align-self: stretch;
}
&.swal2-no-transition {
transition: none !important;
}
div:where(.swal2-popup) {
display: none;
position: relative;
box-sizing: border-box;
grid-template-columns: minmax(0, 100%);
width: $swal2-width;
max-width: 100%;
padding: $swal2-padding;
border: $swal2-border;
border-radius: $swal2-border-radius;
background: $swal2-background;
color: $swal2-color;
font-family: $swal2-font;
font-size: $swal2-font-size;
&:focus {
outline: none;
}
&.swal2-loading {
overflow-y: hidden;
}
}
h2:where(.swal2-title) {
position: $swal2-title-position;
max-width: $swal2-title-max-width;
margin: $swal2-title-margin;
padding: $swal2-title-padding;
color: $swal2-title-color;
font-size: $swal2-title-font-size;
font-weight: $swal2-title-font-weight;
text-align: $swal2-title-text-align;
text-transform: none;
word-wrap: break-word;
}
div:where(.swal2-actions) {
display: flex;
z-index: 1; // prevent success icon from overlapping buttons
box-sizing: border-box;
flex-wrap: $swal2-actions-flex-wrap;
align-items: $swal2-actions-align-items;
justify-content: $swal2-actions-justify-content;
width: $swal2-actions-width;
margin: $swal2-actions-margin;
padding: $swal2-actions-padding;
&:not(.swal2-loading) {
.swal2-styled {
&[disabled] {
opacity: 0.4;
}
&:hover {
background-image: linear-gradient($swal2-button-darken-hover, $swal2-button-darken-hover);
}
&:active {
background-image: linear-gradient($swal2-button-darken-active, $swal2-button-darken-active);
}
}
}
}
div:where(.swal2-loader) {
display: none;
align-items: $swal2-loader-align-items;
justify-content: $swal2-loader-justify-content;
width: $swal2-loader-width;
height: $swal2-loader-height;
margin: $swal2-loader-margin;
animation: $swal2-loader-animation;
border-width: $swal2-loader-border-width;
border-style: $swal2-loader-border-style;
border-radius: $swal2-loader-border-radius;
border-color: $swal2-loader-border-color;
}
button:where(.swal2-styled) {
margin: $swal2-button-margin;
padding: $swal2-button-padding;
transition: $swal2-button-transition;
box-shadow: $swal2-button-box-shadow;
font-weight: $swal2-button-font-weight;
&:not([disabled]) {
cursor: pointer;
}
&.swal2-confirm {
order: $swal2-confirm-button-order;
border: $swal2-confirm-button-border;
border-radius: $swal2-confirm-button-border-radius;
background: initial;
background-color: $swal2-confirm-button-background-color;
color: $swal2-confirm-button-color;
font-size: $swal2-confirm-button-font-size;
&:focus {
box-shadow: $swal2-confirm-button-focus-box-shadow;
}
}
&.swal2-deny {
order: $swal2-deny-button-order;
border: $swal2-deny-button-border;
border-radius: $swal2-deny-button-border-radius;
background: initial;
background-color: $swal2-deny-button-background-color;
color: $swal2-deny-button-color;
font-size: $swal2-deny-button-font-size;
&:focus {
box-shadow: $swal2-deny-button-focus-box-shadow;
}
}
&.swal2-cancel {
order: $swal2-cancel-button-order;
border: $swal2-cancel-button-border;
border-radius: $swal2-cancel-button-border-radius;
background: initial;
background-color: $swal2-cancel-button-background-color;
color: $swal2-cancel-button-color;
font-size: $swal2-cancel-button-font-size;
&:focus {
box-shadow: $swal2-cancel-button-focus-box-shadow;
}
}
&.swal2-default-outline {
&:focus {
box-shadow: $swal2-button-focus-box-shadow;
}
}
&:focus {
outline: $swal2-button-focus-outline;
}
&::-moz-focus-inner {
border: 0;
}
}
div:where(.swal2-footer) {
margin: $swal2-footer-margin;
padding: $swal2-footer-padding;
border-top: 1px solid $swal2-footer-border-color;
color: $swal2-footer-color;
font-size: $swal2-footer-font-size;
text-align: $swal2-footer-text-align;
}
.swal2-timer-progress-bar-container {
position: absolute;
right: 0;
bottom: 0;
left: 0;
grid-column: auto !important;
overflow: hidden;
border-bottom-right-radius: $swal2-border-radius;
border-bottom-left-radius: $swal2-border-radius;
}
div:where(.swal2-timer-progress-bar) {
width: 100%;
height: $swal2-timer-progress-bar-height;
background: $swal2-timer-progress-bar-background;
}
img:where(.swal2-image) {
max-width: 100%;
margin: $swal2-image-margin;
}
button:where(.swal2-close) {
position: $swal2-close-button-position;
z-index: 2; // sweetalert2/issues/1617
align-items: $swal2-close-button-align-items;
justify-content: $swal2-close-button-justify-content;
width: $swal2-close-button-width;
height: $swal2-close-button-height;
margin-top: $swal2-close-button-gap;
margin-right: $swal2-close-button-gap;
margin-bottom: -$swal2-close-button-height;
padding: $swal2-close-button-padding;
overflow: hidden;
transition: $swal2-close-button-transition;
border: $swal2-close-button-border;
border-radius: $swal2-close-button-border-radius;
outline: $swal2-close-button-outline;
background: $swal2-close-button-background;
color: $swal2-close-button-color;
font-family: $swal2-close-button-font-family;
font-size: $swal2-close-button-font-size;
cursor: pointer;
justify-self: $swal2-close-button-justify-self;
&:hover {
transform: $swal2-close-button-hover-transform;
background: $swal2-close-button-hover-background;
color: $swal2-close-button-hover-color;
}
&:focus {
outline: $swal2-close-button-focus-outline;
box-shadow: $swal2-close-button-focus-box-shadow;
}
&::-moz-focus-inner {
border: 0;
}
}
.swal2-html-container {
z-index: 1; // prevent success icon overlapping the content
justify-content: $swal2-html-container-justify-content;
margin: $swal2-html-container-margin;
padding: $swal2-html-container-padding;
overflow: $swal2-html-container-overflow;
color: $swal2-html-container-color;
font-size: $swal2-html-container-font-size;
font-weight: $swal2-html-container-font-weight;
line-height: $swal2-html-container-line-height;
text-align: $swal2-html-container-text-align;
word-wrap: $swal2-html-container-word-wrap;
word-break: $swal2-html-container-word-break;
}
input:where(.swal2-input),
input:where(.swal2-file),
textarea:where(.swal2-textarea),
select:where(.swal2-select),
div:where(.swal2-radio),
label:where(.swal2-checkbox) {
margin: $swal2-input-margin;
}
input:where(.swal2-input),
input:where(.swal2-file),
textarea:where(.swal2-textarea) {
box-sizing: border-box;
width: $swal2-input-width;
transition: $swal2-input-transition;
border: $swal2-input-border;
border-radius: $swal2-input-border-radius;
background: $swal2-input-background;
box-shadow: $swal2-input-box-shadow;
color: $swal2-input-color;
font-size: $swal2-input-font-size;
&.swal2-inputerror {
border-color: $swal2-error !important;
box-shadow: 0 0 2px $swal2-error !important;
}
&:focus {
border: $swal2-input-focus-border;
outline: $swal2-input-focus-outline;
box-shadow: $swal2-input-focus-box-shadow;
}
&::placeholder {
color: #ccc;
}
}
.swal2-range {
margin: $swal2-input-margin;
background: $swal2-background;
input {
width: 80%;
}
output {
width: 20%;
color: $swal2-input-color;
font-weight: 600;
text-align: center;
}
input,
output {
height: $swal2-input-height;
padding: 0;
font-size: $swal2-input-font-size;
line-height: $swal2-input-height;
}
}
.swal2-input {
height: $swal2-input-height;
padding: $swal2-input-padding;
}
.swal2-file {
width: 75%;
margin-right: auto;
margin-left: auto;
background: $swal2-input-background;
font-size: $swal2-input-font-size;
}
.swal2-textarea {
height: $swal2-textarea-height;
padding: $swal2-textarea-padding;
}
.swal2-select {
min-width: 50%;
max-width: 100%;
padding: 0.375em 0.625em;
background: $swal2-input-background;
color: $swal2-input-color;
font-size: $swal2-input-font-size;
}
.swal2-radio,
.swal2-checkbox {
align-items: center;
justify-content: center;
background: $swal2-background;
color: $swal2-input-color;
label {
margin: 0 0.6em;
font-size: $swal2-input-font-size;
}
input {
flex-shrink: 0;
margin: 0 0.4em;
}
}
label:where(.swal2-input-label) {
display: flex;
justify-content: $swal2-input-label-justify-content;
margin: $swal2-input-label-margin;
}
div:where(.swal2-validation-message) {
align-items: $swal2-validation-message-align-items;
justify-content: $swal2-validation-message-justify-content;
margin: $swal2-validation-message-margin;
padding: $swal2-validation-message-padding;
overflow: hidden;
background: $swal2-validation-message-background;
color: $swal2-validation-message-color;
font-size: $swal2-validation-message-font-size;
font-weight: $swal2-validation-message-font-weight;
&::before {
content: '!';
display: inline-block;
width: 1.5em;
min-width: 1.5em;
height: 1.5em;
margin: 0 0.625em;
zoom: $swal2-validation-message-icon-zoom;
border-radius: 50%;
background-color: $swal2-validation-message-icon-background;
color: $swal2-validation-message-icon-color;
font-weight: 600;
line-height: 1.5em;
text-align: center;
}
}
.swal2-progress-steps {
flex-wrap: $swal2-progress-steps-flex-wrap;
align-items: $swal2-progress-steps-align-items;
max-width: $swal2-progress-steps-max-width;
margin: $swal2-progress-steps-margin;
padding: $swal2-progress-steps-padding;
background: $swal2-progress-steps-background;
font-weight: $swal2-progress-steps-font-weight;
li {
display: inline-block;
position: relative;
}
.swal2-progress-step {
z-index: 20;
flex-shrink: 0;
width: $swal2-progress-step-width;
height: $swal2-progress-step-height;
border-radius: $swal2-progress-step-border-radius;
background: $swal2-active-step-background;
color: $swal2-active-step-color;
line-height: $swal2-progress-step-height;
text-align: center;
&.swal2-active-progress-step {
background: $swal2-active-step-background;
~ .swal2-progress-step {
background: $swal2-progress-step-background;
color: $swal2-progress-step-color;
}
~ .swal2-progress-step-line {
background: $swal2-progress-step-background;
}
}
}
.swal2-progress-step-line {
z-index: 10;
flex-shrink: 0;
width: $swal2-progress-steps-distance;
height: 0.4em;
margin: 0 -1px;
background: $swal2-active-step-background;
}
}
}
@import 'icons';
// github.com/sweetalert2/sweetalert2/issues/268
[class^='swal2'] {
-webkit-tap-highlight-color: transparent;
}
.swal2-show {
animation: $swal2-show-animation;
}
.swal2-hide {
animation: $swal2-hide-animation;
}
.swal2-noanimation {
transition: none;
}
// Measure scrollbar width for padding body during modal show/hide
.swal2-scrollbar-measure {
position: absolute;
top: -9999px;
width: 50px;
height: 50px;
overflow: scroll;
}
// Right-to-left support
.swal2-rtl {
.swal2-close {
margin-right: initial;
margin-left: $swal2-close-button-gap;
}
.swal2-timer-progress-bar {
right: 0;
left: auto;
}
}

View File

@@ -0,0 +1,234 @@
// https://stackoverflow.com/a/12335841/1331425
@function strip-units($number) {
@return math.div($number, ($number * 0 + 1));
}
$icon-zoom: math.div(strip-units($swal2-icon-size), 5);
div:where(.swal2-icon) {
position: relative;
box-sizing: content-box;
justify-content: center;
width: $swal2-icon-size;
height: $swal2-icon-size;
margin: $swal2-icon-margin;
zoom: $swal2-icon-zoom;
border: #{0.25em * $icon-zoom} solid transparent;
border-radius: 50%;
border-color: $swal2-icon-border-color;
font-family: $swal2-icon-font-family;
line-height: $swal2-icon-size;
cursor: default;
user-select: none;
.swal2-icon-content {
display: flex;
align-items: center;
font-size: $swal2-icon-font-size;
}
&.swal2-error {
border-color: $swal2-error;
color: $swal2-error;
.swal2-x-mark {
position: relative;
flex-grow: 1;
@if $icon-zoom != 1 {
zoom: $icon-zoom;
}
}
[class^='swal2-x-mark-line'] {
display: block;
position: absolute;
top: 2.3125em;
width: 2.9375em;
height: 0.3125em;
border-radius: 0.125em;
background-color: $swal2-error;
&[class$='left'] {
left: 1.0625em;
transform: rotate(45deg);
}
&[class$='right'] {
right: 1em;
transform: rotate(-45deg);
}
}
// Error icon animation
&.swal2-icon-show {
@if $swal2-icon-animations {
animation: swal2-animate-error-icon 0.5s;
.swal2-x-mark {
animation: swal2-animate-error-x-mark 0.5s;
}
}
}
}
&.swal2-warning {
border-color: lighten($swal2-warning, 7);
color: $swal2-warning;
// Warning icon animation
&.swal2-icon-show {
@if $swal2-icon-animations {
animation: swal2-animate-error-icon 0.5s;
.swal2-icon-content {
animation: swal2-animate-i-mark 0.5s;
}
}
}
}
&.swal2-info {
border-color: lighten($swal2-info, 20);
color: $swal2-info;
// Info icon animation
&.swal2-icon-show {
@if $swal2-icon-animations {
animation: swal2-animate-error-icon 0.5s;
.swal2-icon-content {
animation: swal2-animate-i-mark 0.8s;
}
}
}
}
&.swal2-question {
border-color: lighten($swal2-question, 20);
color: $swal2-question;
// Question icon animation
&.swal2-icon-show {
@if $swal2-icon-animations {
animation: swal2-animate-error-icon 0.5s;
.swal2-icon-content {
animation: swal2-animate-question-mark 0.8s;
}
}
}
}
&.swal2-success {
border-color: $swal2-success;
color: $swal2-success;
[class^='swal2-success-circular-line'] {
// Emulate moving circular line
position: absolute;
width: 3.75em;
height: 7.5em;
border-radius: 50%;
&[class$='left'] {
top: -0.4375em;
left: -2.0635em;
transform: rotate(-45deg);
transform-origin: 3.75em 3.75em;
border-radius: 7.5em 0 0 7.5em;
@if $icon-zoom != 1 {
zoom: $icon-zoom;
}
}
&[class$='right'] {
top: -0.6875em;
left: 1.875em;
transform: rotate(-45deg);
transform-origin: 0 3.75em;
border-radius: 0 7.5em 7.5em 0;
@if $icon-zoom != 1 {
zoom: $icon-zoom;
}
}
}
.swal2-success-ring {
// Ring
position: absolute;
z-index: 2;
top: -0.25em;
left: -0.25em;
box-sizing: content-box;
width: 100%;
height: 100%;
border: 0.25em solid $swal2-success-border;
border-radius: 50%;
@if $icon-zoom != 1 {
zoom: $icon-zoom;
}
}
.swal2-success-fix {
// Hide corners left from animation
position: absolute;
z-index: 1;
top: 0.5em;
left: 1.625em;
width: 0.4375em;
height: 5.625em;
transform: rotate(-45deg);
@if $icon-zoom != 1 {
zoom: $icon-zoom;
}
}
[class^='swal2-success-line'] {
display: block;
position: absolute;
z-index: 2;
height: 0.3125em;
border-radius: 0.125em;
background-color: $swal2-success;
&[class$='tip'] {
top: 2.875em;
left: 0.8125em;
width: 1.5625em;
transform: rotate(45deg);
}
&[class$='long'] {
top: 2.375em;
right: 0.5em;
width: 2.9375em;
transform: rotate(-45deg);
}
@if $icon-zoom != 1 {
zoom: $icon-zoom;
}
}
// Success icon animation
&.swal2-icon-show {
@if $swal2-icon-animations {
.swal2-success-line-tip {
animation: swal2-animate-success-line-tip 0.75s;
}
.swal2-success-line-long {
animation: swal2-animate-success-line-long 0.75s;
}
.swal2-success-circular-line-right {
animation: swal2-rotate-success-circular-line 4.25s ease-in;
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
// https://stackoverflow.com/a/30250161
@mixin not($ignor-list...) {
@if (length($ignor-list) == 1) {
$ignor-list: nth($ignor-list, 1);
}
$not-output: '';
@each $not in $ignor-list {
$not-output: $not-output + ':not(#{$not})'; // stylelint-disable-line scss/no-duplicate-dollar-variables
}
&#{$not-output} {
@content;
}
}

View File

@@ -0,0 +1,8 @@
// base file for including when performing theming
// doesn't include at-rules or root selectors (like body) which allows for more comprehensive extending
@import '../variables';
@import 'mixins';
@import 'toasts';
@import 'body';
@import 'core';

View File

@@ -0,0 +1,83 @@
// Animations
@keyframes swal2-toast-show {
0% {
transform: translateY(-0.625em) rotateZ(2deg);
}
33% {
transform: translateY(0) rotateZ(-2deg);
}
66% {
transform: translateY(0.3125em) rotateZ(2deg);
}
100% {
transform: translateY(0) rotateZ(0deg);
}
}
@keyframes swal2-toast-hide {
100% {
transform: rotateZ(1deg);
opacity: 0;
}
}
@keyframes swal2-toast-animate-success-line-tip {
0% {
top: 0.5625em;
left: 0.0625em;
width: 0;
}
54% {
top: 0.125em;
left: 0.125em;
width: 0;
}
70% {
top: 0.625em;
left: -0.25em;
width: 1.625em;
}
84% {
top: 1.0625em;
left: 0.75em;
width: 0.5em;
}
100% {
top: 1.125em;
left: 0.1875em;
width: 0.75em;
}
}
@keyframes swal2-toast-animate-success-line-long {
0% {
top: 1.625em;
right: 1.375em;
width: 0;
}
65% {
top: 1.25em;
right: 0.9375em;
width: 0;
}
84% {
top: 0.9375em;
right: 0;
width: 1.125em;
}
100% {
top: 0.9375em;
right: 0.1875em;
width: 1.375em;
}
}

View File

@@ -0,0 +1,58 @@
@mixin sweetalert2-toasts-body() {
&.swal2-toast-shown {
.swal2-container {
box-sizing: border-box;
width: $swal2-toast-width;
max-width: 100%;
background-color: transparent;
pointer-events: none;
&.swal2-top {
inset: 0 auto auto 50%;
transform: translateX(-50%);
}
&.swal2-top-end,
&.swal2-top-right {
inset: 0 0 auto auto;
}
&.swal2-top-start,
&.swal2-top-left {
inset: 0 auto auto 0;
}
&.swal2-center-start,
&.swal2-center-left {
inset: 50% auto auto 0;
transform: translateY(-50%);
}
&.swal2-center {
inset: 50% auto auto 50%;
transform: translate(-50%, -50%);
}
&.swal2-center-end,
&.swal2-center-right {
inset: 50% 0 auto auto;
transform: translateY(-50%);
}
&.swal2-bottom-start,
&.swal2-bottom-left {
inset: auto auto 0 0;
}
&.swal2-bottom {
inset: auto auto 0 50%;
transform: translateX(-50%);
}
&.swal2-bottom-end,
&.swal2-bottom-right {
inset: auto 0 0 auto;
}
}
}
}

View File

@@ -0,0 +1,203 @@
.swal2-popup {
&.swal2-toast {
box-sizing: border-box;
grid-column: 1/4 !important;
grid-row: 1/4 !important;
grid-template-columns: min-content auto min-content;
padding: $swal2-toast-padding;
overflow-y: hidden;
background: $swal2-toast-background;
box-shadow: $swal2-toast-box-shadow;
pointer-events: all;
> * {
grid-column: 2;
}
.swal2-title {
margin: $swal2-toast-title-margin;
padding: $swal2-toast-title-padding;
font-size: $swal2-toast-title-font-size;
text-align: initial;
}
.swal2-loading {
justify-content: center;
}
.swal2-input {
height: $swal2-toast-input-height;
margin: $swal2-toast-input-margin;
font-size: $swal2-toast-input-font-size;
}
.swal2-validation-message {
font-size: $swal2-toast-validation-font-size;
}
.swal2-footer {
margin: $swal2-toast-footer-margin;
padding: $swal2-toast-footer-margin;
font-size: $swal2-toast-footer-font-size;
}
.swal2-close {
grid-column: 3/3;
grid-row: 1/99;
align-self: center;
width: $swal2-toast-close-button-width;
height: $swal2-toast-close-button-height;
margin: $swal2-toast-close-button-margin;
font-size: $swal2-toast-close-button-font-size;
}
.swal2-html-container {
margin: $swal2-toast-html-container-margin;
padding: $swal2-toast-html-container-padding;
overflow: initial;
font-size: $swal2-toast-html-container-font-size;
text-align: initial;
&:empty {
padding: 0;
}
}
.swal2-loader {
grid-column: 1;
grid-row: 1/99;
align-self: center;
width: 2em;
height: 2em;
margin: 0.25em;
}
.swal2-icon {
grid-column: 1;
grid-row: 1/99;
align-self: center;
width: 2em;
min-width: 2em;
height: 2em;
margin: 0 0.5em 0 0;
.swal2-icon-content {
display: flex;
align-items: center;
font-size: $swal2-toast-icon-font-size;
font-weight: bold;
}
&.swal2-success {
.swal2-success-ring {
width: 2em;
height: 2em;
}
}
&.swal2-error {
[class^='swal2-x-mark-line'] {
top: 0.875em;
width: 1.375em;
&[class$='left'] {
left: 0.3125em;
}
&[class$='right'] {
right: 0.3125em;
}
}
}
}
.swal2-actions {
justify-content: flex-start;
height: auto;
margin: 0;
margin-top: 0.5em;
padding: 0 0.5em;
}
.swal2-styled {
margin: 0.25em 0.5em;
padding: 0.4em 0.6em;
font-size: $swal2-toast-buttons-font-size;
}
.swal2-success {
border-color: $swal2-success;
[class^='swal2-success-circular-line'] {
// Emulate moving circular line
position: absolute;
width: 1.6em;
height: 3em;
border-radius: 50%;
&[class$='left'] {
top: -0.8em;
left: -0.5em;
transform: rotate(-45deg);
transform-origin: 2em 2em;
border-radius: 4em 0 0 4em;
}
&[class$='right'] {
top: -0.25em;
left: 0.9375em;
transform-origin: 0 1.5em;
border-radius: 0 4em 4em 0;
}
}
.swal2-success-ring {
width: 2em;
height: 2em;
}
.swal2-success-fix {
top: 0;
left: 0.4375em;
width: 0.4375em;
height: 2.6875em;
}
[class^='swal2-success-line'] {
height: 0.3125em;
&[class$='tip'] {
top: 1.125em;
left: 0.1875em;
width: 0.75em;
}
&[class$='long'] {
top: 0.9375em;
right: 0.1875em;
width: 1.375em;
}
}
&.swal2-icon-show {
@if $swal2-icon-animations {
.swal2-success-line-tip {
animation: swal2-toast-animate-success-line-tip 0.75s;
}
.swal2-success-line-long {
animation: swal2-toast-animate-success-line-long 0.75s;
}
}
}
}
&.swal2-show {
animation: $swal2-toast-show-animation;
}
&.swal2-hide {
animation: $swal2-toast-hide-animation;
}
}
}

View File

@@ -0,0 +1,8 @@
export * from './staticMethods/argsToParams.js'
export * from './staticMethods/dom.js'
export * from './staticMethods/fire.js'
export * from './staticMethods/mixin.js'
export * from './staticMethods/showLoading.js'
export * from './staticMethods/timer.js'
export * from './staticMethods/bindClickHandler.js'
export { isValidParameter, isUpdatableParameter, isDeprecatedParameter } from './utils/params.js'

View File

@@ -0,0 +1,21 @@
import { error } from '../utils/utils.js'
const isJqueryElement = (elem) => typeof elem === 'object' && elem.jquery
const isElement = (elem) => elem instanceof Element || isJqueryElement(elem)
export const argsToParams = (args) => {
const params = {}
if (typeof args[0] === 'object' && !isElement(args[0])) {
Object.assign(params, args[0])
} else {
;['title', 'html', 'icon'].forEach((name, index) => {
const arg = args[index]
if (typeof arg === 'string' || isElement(arg)) {
params[name] = arg
} else if (arg !== undefined) {
error(`Unexpected type of ${name}! Expected "string" or "Element", got ${typeof arg}`)
}
})
}
return params
}

View File

@@ -0,0 +1,26 @@
let bodyClickListenerAdded = false
const clickHandlers = {}
/**
* @param {string} attr
*/
export function bindClickHandler(attr = 'data-swal-template') {
clickHandlers[attr] = this
if (!bodyClickListenerAdded) {
document.body.addEventListener('click', bodyClickListener)
bodyClickListenerAdded = true
}
}
const bodyClickListener = (event) => {
for (let el = event.target; el && el !== document; el = el.parentNode) {
for (const attr in clickHandlers) {
const template = el.getAttribute(attr)
if (template) {
clickHandlers[attr].fire({ template })
return
}
}
}
}

View File

@@ -0,0 +1,47 @@
import * as domUtils from '../utils/dom/domUtils.js'
import * as dom from '../utils/dom/index.js'
export {
getContainer,
getPopup,
getTitle,
getHtmlContainer,
getImage,
getIcon,
getIconContent,
getInputLabel,
getCloseButton,
getActions,
getConfirmButton,
getDenyButton,
getCancelButton,
getLoader,
getFooter,
getTimerProgressBar,
getFocusableElements,
getValidationMessage,
getProgressSteps,
isLoading,
} from '../utils/dom/index.js'
/*
* Global function to determine if SweetAlert2 popup is shown
*/
export const isVisible = () => {
return domUtils.isVisible(dom.getPopup())
}
/*
* Global function to click 'Confirm' button
*/
export const clickConfirm = () => dom.getConfirmButton()?.click()
/*
* Global function to click 'Deny' button
*/
export const clickDeny = () => dom.getDenyButton()?.click()
/*
* Global function to click 'Cancel' button
*/
export const clickCancel = () => dom.getCancelButton()?.click()

View File

@@ -0,0 +1,10 @@
/**
* Main method to create a new SweetAlert2 popup
*
* @param {...SweetAlertOptions} args
* @returns {Promise<SweetAlertResult>}
*/
export function fire(...args) {
const Swal = this // eslint-disable-line @typescript-eslint/no-this-alias
return new Swal(...args)
}

View File

@@ -0,0 +1,28 @@
/**
* Returns an extended version of `Swal` containing `params` as defaults.
* Useful for reusing Swal configuration.
*
* For example:
*
* Before:
* const textPromptOptions = { input: 'text', showCancelButton: true }
* const {value: firstName} = await Swal.fire({ ...textPromptOptions, title: 'What is your first name?' })
* const {value: lastName} = await Swal.fire({ ...textPromptOptions, title: 'What is your last name?' })
*
* After:
* const TextPrompt = Swal.mixin({ input: 'text', showCancelButton: true })
* const {value: firstName} = await TextPrompt('What is your first name?')
* const {value: lastName} = await TextPrompt('What is your last name?')
*
* @param {SweetAlertOptions} mixinParams
* @returns {SweetAlert}
*/
export function mixin(mixinParams) {
class MixinSwal extends this {
_main(params, priorityMixinParams) {
return super._main(params, Object.assign({}, mixinParams, priorityMixinParams))
}
}
// @ts-ignore
return MixinSwal
}

View File

@@ -0,0 +1,58 @@
import Swal from '../sweetalert2.js'
import { swalClasses } from '../utils/classes.js'
import * as dom from '../utils/dom/index.js'
/**
* Shows loader (spinner), this is useful with AJAX requests.
* By default the loader be shown instead of the "Confirm" button.
*
* @param {HTMLButtonElement | null} [buttonToReplace]
*/
const showLoading = (buttonToReplace) => {
let popup = dom.getPopup()
if (!popup) {
new Swal() // eslint-disable-line no-new
}
popup = dom.getPopup()
if (!popup) {
return
}
const loader = dom.getLoader()
if (dom.isToast()) {
dom.hide(dom.getIcon())
} else {
replaceButton(popup, buttonToReplace)
}
dom.show(loader)
popup.setAttribute('data-loading', 'true')
popup.setAttribute('aria-busy', 'true')
popup.focus()
}
/**
* @param {HTMLElement} popup
* @param {HTMLButtonElement | null} [buttonToReplace]
*/
const replaceButton = (popup, buttonToReplace) => {
const actions = dom.getActions()
const loader = dom.getLoader()
if (!actions || !loader) {
return
}
if (!buttonToReplace && dom.isVisible(dom.getConfirmButton())) {
buttonToReplace = dom.getConfirmButton()
}
dom.show(actions)
if (buttonToReplace) {
dom.hide(buttonToReplace)
loader.setAttribute('data-button-to-replace', buttonToReplace.className)
actions.insertBefore(loader, buttonToReplace)
}
dom.addClass([popup, actions], swalClasses.loading)
}
export { showLoading, showLoading as enableLoading }

View File

@@ -0,0 +1,76 @@
import globalState from '../globalState.js'
import { animateTimerProgressBar, stopTimerProgressBar } from '../utils/dom/domUtils.js'
/**
* If `timer` parameter is set, returns number of milliseconds of timer remained.
* Otherwise, returns undefined.
*
* @returns {number | undefined}
*/
export const getTimerLeft = () => {
return globalState.timeout && globalState.timeout.getTimerLeft()
}
/**
* Stop timer. Returns number of milliseconds of timer remained.
* If `timer` parameter isn't set, returns undefined.
*
* @returns {number | undefined}
*/
export const stopTimer = () => {
if (globalState.timeout) {
stopTimerProgressBar()
return globalState.timeout.stop()
}
}
/**
* Resume timer. Returns number of milliseconds of timer remained.
* If `timer` parameter isn't set, returns undefined.
*
* @returns {number | undefined}
*/
export const resumeTimer = () => {
if (globalState.timeout) {
const remaining = globalState.timeout.start()
animateTimerProgressBar(remaining)
return remaining
}
}
/**
* Resume timer. Returns number of milliseconds of timer remained.
* If `timer` parameter isn't set, returns undefined.
*
* @returns {number | undefined}
*/
export const toggleTimer = () => {
const timer = globalState.timeout
return timer && (timer.running ? stopTimer() : resumeTimer())
}
/**
* Increase timer. Returns number of milliseconds of an updated timer.
* If `timer` parameter isn't set, returns undefined.
*
* @param {number} ms
* @returns {number | undefined}
*/
export const increaseTimer = (ms) => {
if (globalState.timeout) {
const remaining = globalState.timeout.increase(ms)
animateTimerProgressBar(remaining, true)
return remaining
}
}
/**
* Check if timer is running. Returns true if timer is running
* or false if timer is paused or stopped.
* If `timer` parameter isn't set, returns undefined
*
* @returns {boolean}
*/
export const isTimerRunning = () => {
return !!(globalState.timeout && globalState.timeout.isRunning())
}

View File

@@ -0,0 +1,7 @@
import SweetAlert from './SweetAlert.js'
const Swal = SweetAlert
// @ts-ignore
Swal.default = Swal
export default Swal

View File

@@ -0,0 +1,10 @@
// SweetAlert2
// github.com/sweetalert2/sweetalert2
@import 'scss/theming';
@import 'scss/animations';
body {
@include sweetalert2-body();
@include sweetalert2-toasts-body();
}

View File

@@ -0,0 +1,39 @@
/**
* @typedef { import('./SweetAlert').SweetAlert } SweetAlert
* @typedef { import('sweetalert2').SweetAlertOptions } SweetAlertOptions
* @typedef { import('sweetalert2').SweetAlertIcon } SweetAlertIcon
* @typedef { import('sweetalert2').SweetAlertInput } SweetAlertInput
* @typedef { import('sweetalert2').SweetAlertResult } SweetAlertResult
* @typedef { import('sweetalert2').SweetAlertOptions['inputValue'] } SweetAlertInputValue
*/
/**
* @typedef { import('./utils/Timer').default } Timer
*/
/**
* @typedef GlobalState
* @property {SweetAlert} [currentInstance]
* @property {Element | null} [previousActiveElement]
* @property {Timer} [timeout]
* @property {NodeJS.Timeout} [restoreFocusTimeout]
* @property {(this: HTMLElement, event: KeyboardEvent) => any} [keydownHandler]
* @property {HTMLElement | (Window & typeof globalThis)} [keydownTarget]
* @property {boolean} [keydownHandlerAdded]
* @property {boolean} [keydownListenerCapture]
* @property {Function} [swalCloseEventFinishedCallback]
*/
/**
* @typedef DomCache
* @property {HTMLElement} popup
* @property {HTMLElement} container
* @property {HTMLElement} actions
* @property {HTMLElement} confirmButton
* @property {HTMLElement} denyButton
* @property {HTMLElement} cancelButton
* @property {HTMLElement} loader
* @property {HTMLElement} closeButton
* @property {HTMLElement} validationMessage
* @property {HTMLElement} progressSteps
*/

View File

@@ -0,0 +1,10 @@
/** @typedef {'cancel' | 'backdrop' | 'close' | 'esc' | 'timer'} DismissReason */
/** @type {Record<DismissReason, DismissReason>} */
export const DismissReason = Object.freeze({
cancel: 'cancel',
backdrop: 'backdrop',
close: 'close',
esc: 'esc',
timer: 'timer',
})

View File

@@ -0,0 +1,71 @@
export default class Timer {
/**
* @param {Function} callback
* @param {number} delay
*/
constructor(callback, delay) {
this.callback = callback
this.remaining = delay
this.running = false
this.start()
}
/**
* @returns {number}
*/
start() {
if (!this.running) {
this.running = true
this.started = new Date()
this.id = setTimeout(this.callback, this.remaining)
}
return this.remaining
}
/**
* @returns {number}
*/
stop() {
if (this.started && this.running) {
this.running = false
clearTimeout(this.id)
this.remaining -= new Date().getTime() - this.started.getTime()
}
return this.remaining
}
/**
* @param {number} n
* @returns {number}
*/
increase(n) {
const running = this.running
if (running) {
this.stop()
}
this.remaining += n
if (running) {
this.start()
}
return this.remaining
}
/**
* @returns {number}
*/
getTimerLeft() {
if (this.running) {
this.stop()
this.start()
}
return this.remaining
}
/**
* @returns {boolean}
*/
isRunning() {
return this.running
}
}

View File

@@ -0,0 +1,32 @@
import { getContainer } from './dom/getters.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 = 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')
})
}
export 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')
}
})
}

View File

@@ -0,0 +1,167 @@
export 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<SwalClass, string>} SwalClasses
*/
/**
* @typedef {'success' | 'warning' | 'info' | 'question' | 'error'} SwalIcon
* @typedef {Record<SwalIcon, string>} 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',
]
export const swalClasses = classNames.reduce((acc, className) => {
acc[className] = swalPrefix + className
return acc
}, /** @type {SwalClasses} */ ({}))
/** @type {SwalIcon[]} */
const icons = ['success', 'warning', 'info', 'question', 'error']
export const iconTypes = icons.reduce((acc, icon) => {
acc[icon] = swalPrefix + icon
return acc
}, /** @type {SwalIcons} */ ({}))

View File

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

View File

@@ -0,0 +1,25 @@
import { isNodeEnv } from '../isNodeEnv.js'
/**
* @returns {'webkitAnimationEnd' | 'animationend' | false}
*/
export const animationEndEvent = (() => {
// Prevent run in Node env
if (isNodeEnv()) {
return false
}
const testEl = document.createElement('div')
// Chrome, Safari and Opera
if (typeof testEl.style.webkitAnimation !== 'undefined') {
return 'webkitAnimationEnd'
}
// Standard syntax
if (typeof testEl.style.animation !== 'undefined') {
return 'animationend'
}
return false
})()

View File

@@ -0,0 +1,323 @@
import { iconTypes, swalClasses } from '../classes.js'
import { warn } from '../utils.js'
import { getCancelButton, getConfirmButton, getDenyButton, getTimerProgressBar } from './getters.js'
/**
* Securely set innerHTML of an element
* https://github.com/sweetalert2/sweetalert2/issues/1926
*
* @param {HTMLElement} elem
* @param {string} html
*/
export const setInnerHtml = (elem, html) => {
elem.textContent = ''
if (html) {
const parser = new DOMParser()
const parsed = parser.parseFromString(html, `text/html`)
const head = parsed.querySelector('head')
head &&
Array.from(head.childNodes).forEach((child) => {
elem.appendChild(child)
})
const body = parsed.querySelector('body')
body &&
Array.from(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}
*/
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
}
/**
* @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
*/
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) {
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 | SweetAlertInput} inputClass
* @returns {HTMLInputElement | null}
*/
export const getInput = (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
*/
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
}
}
/**
* @param {HTMLElement | HTMLElement[] | null} target
* @param {string | string[] | readonly string[] | undefined} classList
* @param {boolean} condition
*/
export 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[] | undefined} classList
*/
export const addClass = (target, classList) => {
toggleClass(target, classList, true)
}
/**
* @param {HTMLElement | HTMLElement[] | null} target
* @param {string | string[] | readonly string[] | undefined} classList
*/
export 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}
*/
export 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
*/
export const applyNumericalStyle = (elem, property, value) => {
if (value === `${parseInt(value)}`) {
value = parseInt(value)
}
if (value || parseInt(value) === 0) {
elem.style.setProperty(property, typeof value === 'number' ? `${value}px` : value)
} else {
elem.style.removeProperty(property)
}
}
/**
* @param {HTMLElement | null} elem
* @param {string} display
*/
export const show = (elem, display = 'flex') => {
elem && (elem.style.display = display)
}
/**
* @param {HTMLElement | null} elem
*/
export const hide = (elem) => {
elem && (elem.style.display = 'none')
}
/**
* @param {HTMLElement | null} elem
* @param {string} display
*/
export const showWhenInnerHtmlPresent = (elem, display = 'block') => {
if (!elem) {
return
}
new MutationObserver(() => {
toggle(elem, elem.innerHTML, display)
}).observe(elem, { childList: true, subtree: true })
}
/**
* @param {HTMLElement} parent
* @param {string} selector
* @param {string} property
* @param {string} value
*/
export const setStyle = (parent, selector, property, value) => {
/** @type {HTMLElement | null} */
const el = parent.querySelector(selector)
if (el) {
el.style.setProperty(property, value)
}
}
/**
* @param {HTMLElement} elem
* @param {any} condition
* @param {string} display
*/
export const toggle = (elem, condition, display = 'flex') => {
condition ? show(elem, display) : hide(elem)
}
/**
* borrowed from jquery $(elem).is(':visible') implementation
*
* @param {HTMLElement | null} elem
* @returns {boolean}
*/
export const isVisible = (elem) => !!(elem && (elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length))
/**
* @returns {boolean}
*/
export const allButtonsAreHidden = () =>
!isVisible(getConfirmButton()) && !isVisible(getDenyButton()) && !isVisible(getCancelButton())
/**
* @param {HTMLElement} elem
* @returns {boolean}
*/
export const isScrollable = (elem) => !!(elem.scrollHeight > elem.clientHeight)
/**
* borrowed from https://stackoverflow.com/a/46352119
*
* @param {HTMLElement} elem
* @returns {boolean}
*/
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
}
/**
* @param {number} timer
* @param {boolean} reset
*/
export const animateTimerProgressBar = (timer, reset = false) => {
const timerProgressBar = getTimerProgressBar()
if (!timerProgressBar) {
return
}
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()
if (!timerProgressBar) {
return
}
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}%`
}

View File

@@ -0,0 +1,198 @@
import { swalClasses } from '../classes.js'
import { hasClass, isVisible } from './domUtils.js'
/**
* Gets the popup container which contains the backdrop and the popup itself.
*
* @returns {HTMLElement | null}
*/
export const getContainer = () => document.body.querySelector(`.${swalClasses.container}`)
/**
* @param {string} selectorString
* @returns {HTMLElement | null}
*/
export 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}
*/
export const getPopup = () => elementByClass(swalClasses.popup)
/**
* @returns {HTMLElement | null}
*/
export const getIcon = () => elementByClass(swalClasses.icon)
/**
* @returns {HTMLElement | null}
*/
export const getIconContent = () => elementByClass(swalClasses['icon-content'])
/**
* @returns {HTMLElement | null}
*/
export const getTitle = () => elementByClass(swalClasses.title)
/**
* @returns {HTMLElement | null}
*/
export const getHtmlContainer = () => elementByClass(swalClasses['html-container'])
/**
* @returns {HTMLElement | null}
*/
export const getImage = () => elementByClass(swalClasses.image)
/**
* @returns {HTMLElement | null}
*/
export const getProgressSteps = () => elementByClass(swalClasses['progress-steps'])
/**
* @returns {HTMLElement | null}
*/
export const getValidationMessage = () => elementByClass(swalClasses['validation-message'])
/**
* @returns {HTMLButtonElement | null}
*/
export const getConfirmButton = () =>
/** @type {HTMLButtonElement} */ (elementBySelector(`.${swalClasses.actions} .${swalClasses.confirm}`))
/**
* @returns {HTMLButtonElement | null}
*/
export const getCancelButton = () =>
/** @type {HTMLButtonElement} */ (elementBySelector(`.${swalClasses.actions} .${swalClasses.cancel}`))
/**
* @returns {HTMLButtonElement | null}
*/
export const getDenyButton = () =>
/** @type {HTMLButtonElement} */ (elementBySelector(`.${swalClasses.actions} .${swalClasses.deny}`))
/**
* @returns {HTMLElement | null}
*/
export const getInputLabel = () => elementByClass(swalClasses['input-label'])
/**
* @returns {HTMLElement | null}
*/
export const getLoader = () => elementBySelector(`.${swalClasses.loader}`)
/**
* @returns {HTMLElement | null}
*/
export const getActions = () => elementByClass(swalClasses.actions)
/**
* @returns {HTMLElement | null}
*/
export const getFooter = () => elementByClass(swalClasses.footer)
/**
* @returns {HTMLElement | null}
*/
export const getTimerProgressBar = () => elementByClass(swalClasses['timer-progress-bar'])
/**
* @returns {HTMLElement | null}
*/
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
`
/**
* @returns {HTMLElement[]}
*/
export const getFocusableElements = () => {
const popup = getPopup()
if (!popup) {
return []
}
/** @type {NodeListOf<HTMLElement>} */
const focusableElementsWithTabindex = popup.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') || '0')
const tabindexB = parseInt(b.getAttribute('tabindex') || '0')
if (tabindexA > tabindexB) {
return 1
} else if (tabindexA < tabindexB) {
return -1
}
return 0
})
/** @type {NodeListOf<HTMLElement>} */
const otherFocusableElements = popup.querySelectorAll(focusable)
const otherFocusableElementsFiltered = Array.from(otherFocusableElements).filter(
(el) => el.getAttribute('tabindex') !== '-1'
)
return [...new Set(focusableElementsWithTabindexSorted.concat(otherFocusableElementsFiltered))].filter((el) =>
isVisible(el)
)
}
/**
* @returns {boolean}
*/
export const isModal = () => {
return (
hasClass(document.body, swalClasses.shown) &&
!hasClass(document.body, swalClasses['toast-shown']) &&
!hasClass(document.body, swalClasses['no-backdrop'])
)
}
/**
* @returns {boolean}
*/
export const isToast = () => {
const popup = getPopup()
if (!popup) {
return false
}
return hasClass(popup, swalClasses.toast)
}
/**
* @returns {boolean}
*/
export const isLoading = () => {
const popup = getPopup()
if (!popup) {
return false
}
return popup.hasAttribute('data-loading')
}

View File

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

View File

@@ -0,0 +1,151 @@
import globalState from '../../globalState.js'
import { swalClasses } from '../classes.js'
import { isNodeEnv } from '../isNodeEnv.js'
import { error } from '../utils.js'
import { addClass, getDirectChildByClass, removeClass, setInnerHtml } from './domUtils.js'
import { getContainer, getPopup } from './getters.js'
const sweetHTML = `
<div aria-labelledby="${swalClasses.title}" aria-describedby="${swalClasses['html-container']}" class="${swalClasses.popup}" tabindex="-1">
<button type="button" class="${swalClasses.close}"></button>
<ul class="${swalClasses['progress-steps']}"></ul>
<div class="${swalClasses.icon}"></div>
<img class="${swalClasses.image}" />
<h2 class="${swalClasses.title}" id="${swalClasses.title}"></h2>
<div class="${swalClasses['html-container']}" id="${swalClasses['html-container']}"></div>
<input class="${swalClasses.input}" id="${swalClasses.input}" />
<input type="file" class="${swalClasses.file}" />
<div class="${swalClasses.range}">
<input type="range" />
<output></output>
</div>
<select class="${swalClasses.select}" id="${swalClasses.select}"></select>
<div class="${swalClasses.radio}"></div>
<label class="${swalClasses.checkbox}">
<input type="checkbox" id="${swalClasses.checkbox}" />
<span class="${swalClasses.label}"></span>
</label>
<textarea class="${swalClasses.textarea}" id="${swalClasses.textarea}"></textarea>
<div class="${swalClasses['validation-message']}" id="${swalClasses['validation-message']}"></div>
<div class="${swalClasses.actions}">
<div class="${swalClasses.loader}"></div>
<button type="button" class="${swalClasses.confirm}"></button>
<button type="button" class="${swalClasses.deny}"></button>
<button type="button" class="${swalClasses.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, '')
/**
* @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 = () => {
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
file.onchange = resetValidationMessage
select.onchange = resetValidationMessage
checkbox.onchange = resetValidationMessage
textarea.oninput = resetValidationMessage
range.oninput = () => {
resetValidationMessage()
rangeOutput.value = range.value
}
range.onchange = () => {
resetValidationMessage()
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
*/
export const init = (params) => {
// Clean up the old popup container if it exists
const oldContainerExisted = resetOldContainer()
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,239 @@
import { showLoading } from '../../staticMethods/showLoading.js'
import { swalClasses } from '../classes.js'
import { asPromise, error, hasToPromiseFn, isPromise } from '../utils.js'
import { getDirectChildByClass } from './domUtils.js'
import * as dom from './index.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const handleInputOptionsAndValue = (instance, params) => {
if (params.input === 'select' || params.input === 'radio') {
handleInputOptions(instance, params)
} else if (
['text', 'email', 'number', 'tel', 'textarea'].some((i) => i === params.input) &&
(hasToPromiseFn(params.inputValue) || isPromise(params.inputValue))
) {
showLoading(dom.getConfirmButton())
handleInputValue(instance, params)
}
}
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} innerParams
* @returns {SweetAlertInputValue}
*/
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
}
}
/**
* @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 && 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 = dom.getPopup()
if (!popup) {
return
}
/**
* @param {Record<string, any>} inputOptions
*/
const processInputOptions = (inputOptions) => {
if (params.input === 'select') {
populateSelectOptions(popup, formatInputOptions(inputOptions), params)
} else if (params.input === 'radio') {
populateRadioOptions(popup, formatInputOptions(inputOptions), params)
}
}
if (hasToPromiseFn(params.inputOptions) || isPromise(params.inputOptions)) {
showLoading(dom.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()
if (!input) {
return
}
dom.hide(input)
asPromise(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()
})
}
/**
* @param {HTMLElement} popup
* @param {InputOptionFlattened[]} inputOptions
* @param {SweetAlertOptions} params
*/
function populateSelectOptions(popup, inputOptions, params) {
const select = getDirectChildByClass(popup, swalClasses.select)
if (!select) {
return
}
/**
* @param {HTMLElement} parent
* @param {string} optionLabel
* @param {string} optionValue
*/
const renderOption = (parent, optionLabel, optionValue) => {
const option = document.createElement('option')
option.value = optionValue
dom.setInnerHtml(option, optionLabel)
option.selected = isSelected(optionValue, params.inputValue)
parent.appendChild(option)
}
inputOptions.forEach((inputOption) => {
const optionValue = inputOption[0]
const optionLabel = inputOption[1]
// <optgroup> 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 <optgroup>
if (Array.isArray(optionLabel)) {
// if it is an array, then it is an <optgroup>
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 <option>
renderOption(select, optionLabel, optionValue)
}
})
select.focus()
}
/**
* @param {HTMLElement} popup
* @param {InputOptionFlattened[]} inputOptions
* @param {SweetAlertOptions} params
*/
function populateRadioOptions(popup, inputOptions, params) {
const radio = getDirectChildByClass(popup, swalClasses.radio)
if (!radio) {
return
}
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 (isSelected(radioValue, params.inputValue)) {
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 {Record<string, any>} inputOptions
* @typedef {string[]} InputOptionFlattened
* @returns {InputOptionFlattened[]}
*/
const formatInputOptions = (inputOptions) => {
/** @type {InputOptionFlattened[]} */
const result = []
if (inputOptions instanceof Map) {
inputOptions.forEach((value, key) => {
let valueFormatted = value
if (typeof valueFormatted === 'object') {
// case of <optgroup>
valueFormatted = formatInputOptions(valueFormatted)
}
result.push([key, valueFormatted])
})
} else {
Object.keys(inputOptions).forEach((key) => {
let valueFormatted = inputOptions[key]
if (typeof valueFormatted === 'object') {
// case of <optgroup>
valueFormatted = formatInputOptions(valueFormatted)
}
result.push([key, valueFormatted])
})
}
return result
}
/**
* @param {string} optionValue
* @param {SweetAlertInputValue} inputValue
* @returns {boolean}
*/
const isSelected = (optionValue, inputValue) => {
return !!inputValue && inputValue.toString() === optionValue.toString()
}

View File

@@ -0,0 +1,53 @@
import { setInnerHtml } from './domUtils.js'
/**
* @param {HTMLElement | object | string} param
* @param {HTMLElement} target
*/
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)
}
}
/**
* @param {any} 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 {any} 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))
}
}

View File

@@ -0,0 +1,35 @@
import { getPopup } from '../getters.js'
import { renderActions } from './renderActions.js'
import { renderCloseButton } from './renderCloseButton.js'
import { renderContainer } from './renderContainer.js'
import { renderContent } from './renderContent.js'
import { renderFooter } from './renderFooter.js'
import { renderIcon } from './renderIcon.js'
import { renderImage } from './renderImage.js'
import { renderPopup } from './renderPopup.js'
import { renderProgressSteps } from './renderProgressSteps.js'
import { renderTitle } from './renderTitle.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export 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)
const popup = getPopup()
if (typeof params.didRender === 'function' && popup) {
params.didRender(popup)
}
}

View File

@@ -0,0 +1,109 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { capitalizeFirstLetter } from '../../utils.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderActions = (instance, params) => {
const actions = dom.getActions()
const loader = dom.getLoader()
if (!actions || !loader) {
return
}
// Actions (buttons) wrapper
if (!params.showConfirmButton && !params.showDenyButton && !params.showCancelButton) {
dom.hide(actions)
} else {
dom.show(actions)
}
// Custom class
dom.applyCustomClass(actions, params, 'actions')
// Render all the buttons
renderButtons(actions, loader, params)
// Loader
dom.setInnerHtml(loader, params.loaderHtml || '')
dom.applyCustomClass(loader, params, 'loader')
}
/**
* @param {HTMLElement} actions
* @param {HTMLElement} loader
* @param {SweetAlertOptions} params
*/
function renderButtons(actions, loader, params) {
const confirmButton = dom.getConfirmButton()
const denyButton = dom.getDenyButton()
const cancelButton = dom.getCancelButton()
if (!confirmButton || !denyButton || !cancelButton) {
return
}
// 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) {
dom.removeClass([confirmButton, denyButton, cancelButton], swalClasses.styled)
return
}
dom.addClass([confirmButton, denyButton, cancelButton], swalClasses.styled)
// Buttons background colors
if (params.confirmButtonColor) {
confirmButton.style.backgroundColor = params.confirmButtonColor
dom.addClass(confirmButton, swalClasses['default-outline'])
}
if (params.denyButtonColor) {
denyButton.style.backgroundColor = params.denyButtonColor
dom.addClass(denyButton, swalClasses['default-outline'])
}
if (params.cancelButtonColor) {
cancelButton.style.backgroundColor = params.cancelButtonColor
dom.addClass(cancelButton, swalClasses['default-outline'])
}
}
/**
* @param {HTMLElement} button
* @param {'confirm' | 'deny' | 'cancel'} buttonType
* @param {SweetAlertOptions} params
*/
function renderButton(button, buttonType, params) {
const buttonName = /** @type {'Confirm' | 'Deny' | 'Cancel'} */ (capitalizeFirstLetter(buttonType))
dom.toggle(button, params[`show${buttonName}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`)
}

View File

@@ -0,0 +1,20 @@
import * as dom from '../../dom/index.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderCloseButton = (instance, params) => {
const closeButton = dom.getCloseButton()
if (!closeButton) {
return
}
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,62 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { warn } from '../../utils.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderContainer = (instance, params) => {
const container = dom.getContainer()
if (!container) {
return
}
handleBackdropParam(container, params.backdrop)
handlePositionParam(container, params.position)
handleGrowParam(container, params.grow)
// Custom class
dom.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) {
dom.addClass([document.documentElement, document.body], swalClasses['no-backdrop'])
}
}
/**
* @param {HTMLElement} container
* @param {SweetAlertOptions['position']} position
*/
function handlePositionParam(container, position) {
if (!position) {
return
}
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)
}
}
/**
* @param {HTMLElement} container
* @param {SweetAlertOptions['grow']} grow
*/
function handleGrowParam(container, grow) {
if (!grow) {
return
}
dom.addClass(container, swalClasses[`grow-${grow}`])
}

View File

@@ -0,0 +1,36 @@
import * as dom from '../../dom/index.js'
import { renderInput } from './renderInput.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderContent = (instance, params) => {
const htmlContainer = dom.getHtmlContainer()
if (!htmlContainer) {
return
}
dom.showWhenInnerHtmlPresent(htmlContainer)
dom.applyCustomClass(htmlContainer, params, 'htmlContainer')
// Content as HTML
if (params.html) {
dom.parseHtmlToContainer(params.html, htmlContainer)
dom.show(htmlContainer, 'block')
}
// Content as plain text
else if (params.text) {
htmlContainer.textContent = params.text
dom.show(htmlContainer, 'block')
}
// No content
else {
dom.hide(htmlContainer)
}
renderInput(instance, params)
}

View File

@@ -0,0 +1,23 @@
import * as dom from '../../dom/index.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderFooter = (instance, params) => {
const footer = dom.getFooter()
if (!footer) {
return
}
dom.showWhenInnerHtmlPresent(footer)
dom.toggle(footer, params.footer, 'block')
if (params.footer) {
dom.parseHtmlToContainer(params.footer, footer)
}
// Custom class
dom.applyCustomClass(footer, params, 'footer')
}

View File

@@ -0,0 +1,154 @@
import privateProps from '../../../privateProps.js'
import { iconTypes, swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { error } from '../../utils.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderIcon = (instance, params) => {
const innerParams = privateProps.innerParams.get(instance)
const icon = dom.getIcon()
if (!icon) {
return
}
// 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) {
dom.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}"`)
dom.hide(icon)
return
}
dom.show(icon)
// Custom or default content
setContent(icon, params)
applyStyles(icon, params)
// Animate icon
dom.addClass(icon, params.showClass && params.showClass.icon)
}
/**
* @param {HTMLElement} icon
* @param {SweetAlertOptions} params
*/
const applyStyles = (icon, params) => {
for (const [iconType, iconClassName] of Object.entries(iconTypes)) {
if (params.icon !== iconType) {
dom.removeClass(icon, iconClassName)
}
}
dom.addClass(icon, params.icon && iconTypes[params.icon])
// Icon color
setColor(icon, params)
// Success icon background color
adjustSuccessIconBackgroundColor()
// Custom class
dom.applyCustomClass(icon, params, 'icon')
}
// Adjust success icon background color to match the popup background color
const adjustSuccessIconBackgroundColor = () => {
const popup = dom.getPopup()
if (!popup) {
return
}
const popupBackgroundColor = window.getComputedStyle(popup).getPropertyValue('background-color')
/** @type {NodeListOf<HTMLElement>} */
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 = `
<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>
`
const errorIconHtml = `
<span class="swal2-x-mark">
<span class="swal2-x-mark-line-left"></span>
<span class="swal2-x-mark-line-right"></span>
</span>
`
/**
* @param {HTMLElement} icon
* @param {SweetAlertOptions} params
*/
const setContent = (icon, params) => {
if (!params.icon && !params.iconHtml) {
return
}
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 if (params.icon) {
const defaultIconHtml = {
question: '?',
warning: '!',
info: 'i',
}
newContent = iconContent(defaultIconHtml[params.icon])
}
if (oldContent.trim() !== newContent.trim()) {
dom.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',
]) {
dom.setStyle(icon, sel, 'background-color', params.iconColor)
}
dom.setStyle(icon, '.swal2-success-ring', 'border-color', params.iconColor)
}
/**
* @param {string} content
* @returns {string}
*/
const iconContent = (content) => `<div class="${swalClasses['icon-content']}">${content}</div>`

View File

@@ -0,0 +1,32 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderImage = (instance, params) => {
const image = dom.getImage()
if (!image) {
return
}
if (!params.imageUrl) {
dom.hide(image)
return
}
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,305 @@
/// <reference path="../../../../sweetalert2.d.ts"/>
/**
* @typedef { HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement } Input
* @typedef { 'input' | 'file' | 'range' | 'select' | 'radio' | 'checkbox' | 'textarea' } InputClass
*/
import privateProps from '../../../privateProps.js'
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { error, isPromise, warn } from '../../utils.js'
/** @type {InputClass[]} */
const inputClasses = ['input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea']
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderInput = (instance, params) => {
const popup = dom.getPopup()
if (!popup) {
return
}
const innerParams = privateProps.innerParams.get(instance)
const rerender = !innerParams || params.input !== innerParams.input
inputClasses.forEach((inputClass) => {
const inputContainer = dom.getDirectChildByClass(popup, swalClasses[inputClass])
if (!inputContainer) {
return
}
// set attributes
setAttributes(inputClass, params.inputAttributes)
// set class
inputContainer.className = swalClasses[inputClass]
if (rerender) {
dom.hide(inputContainer)
}
})
if (params.input) {
if (rerender) {
showInput(params)
}
// set custom class
setCustomClass(params)
}
}
/**
* @param {SweetAlertOptions} params
*/
const showInput = (params) => {
if (!params.input) {
return
}
if (!renderInputType[params.input]) {
error(`Unexpected type of input! Expected ${Object.keys(renderInputType).join(' | ')}, got "${params.input}"`)
return
}
const inputContainer = getInputContainer(params.input)
const input = renderInputType[params.input](inputContainer, params)
dom.show(inputContainer)
// input autofocus
if (params.inputAutoFocus) {
setTimeout(() => {
dom.focusInput(input)
})
}
}
/**
* @param {HTMLInputElement} input
*/
const removeAttributes = (input) => {
for (let i = 0; i < input.attributes.length; i++) {
const attrName = input.attributes[i].name
if (!['id', 'type', 'value', 'style'].includes(attrName)) {
input.removeAttribute(attrName)
}
}
}
/**
* @param {InputClass} inputClass
* @param {SweetAlertOptions['inputAttributes']} inputAttributes
*/
const setAttributes = (inputClass, inputAttributes) => {
const input = dom.getInput(dom.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') {
dom.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) {
const label = document.createElement('label')
const labelClass = swalClasses['input-label']
label.setAttribute('for', input.id)
label.className = labelClass
if (typeof params.customClass === 'object') {
dom.addClass(label, params.customClass.inputLabel)
}
label.innerText = params.inputLabel
prependTo.insertAdjacentElement('beforebegin', label)
}
}
/**
* @param {SweetAlertOptions['input']} inputType
* @returns {HTMLElement}
*/
const getInputContainer = (inputType) => {
return dom.getDirectChildByClass(dom.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<SweetAlertInput, (input: Input | HTMLElement, params: SweetAlertOptions) => Input>} */
const renderInputType = {}
/**
* @param {HTMLInputElement} input
* @param {SweetAlertOptions} params
* @returns {HTMLInputElement}
*/
renderInputType.text =
renderInputType.email =
renderInputType.password =
renderInputType.number =
renderInputType.tel =
renderInputType.url =
renderInputType.search =
renderInputType.date =
renderInputType['datetime-local'] =
renderInputType.time =
renderInputType.week =
renderInputType.month =
(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')
dom.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 = dom.getInput(dom.getPopup(), 'checkbox')
checkbox.value = '1'
checkbox.checked = Boolean(params.inputValue)
const label = checkboxContainer.querySelector('span')
dom.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(dom.getPopup()).width)
const textareaResizeHandler = () => {
// check if texarea is still in document (i.e. popup wasn't closed in the meantime)
if (!document.body.contains(textarea)) {
return
}
const textareaWidth = textarea.offsetWidth + getMargin(textarea)
if (textareaWidth > initialPopupWidth) {
dom.getPopup().style.width = `${textareaWidth}px`
} else {
dom.applyNumericalStyle(dom.getPopup(), 'width', params.width)
}
}
new MutationObserver(textareaResizeHandler).observe(textarea, {
attributes: true,
attributeFilter: ['style'],
})
}
})
return textarea
}

View File

@@ -0,0 +1,71 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderPopup = (instance, params) => {
const container = dom.getContainer()
const popup = dom.getPopup()
if (!container || !popup) {
return
}
// Width
// https://github.com/sweetalert2/sweetalert2/issues/2170
if (params.toast) {
dom.applyNumericalStyle(container, 'width', params.width)
popup.style.width = '100%'
const loader = dom.getLoader()
loader && popup.insertBefore(loader, dom.getIcon())
} else {
dom.applyNumericalStyle(popup, 'width', params.width)
}
// Padding
dom.applyNumericalStyle(popup, 'padding', params.padding)
// Color
if (params.color) {
popup.style.color = params.color
}
// Background
if (params.background) {
popup.style.background = params.background
}
dom.hide(dom.getValidationMessage())
// Classes
addClasses(popup, params)
}
/**
* @param {HTMLElement} popup
* @param {SweetAlertOptions} params
*/
const addClasses = (popup, params) => {
const showClass = params.showClass || {}
// Default Class + showClass when updating Swal.update({})
popup.className = `${swalClasses.popup} ${dom.isVisible(popup) ? 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,67 @@
import { swalClasses } from '../../classes.js'
import * as dom from '../../dom/index.js'
import { warn } from '../../utils.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderProgressSteps = (instance, params) => {
const progressStepsContainer = dom.getProgressSteps()
if (!progressStepsContainer) {
return
}
const { progressSteps, currentProgressStep } = params
if (!progressSteps || progressSteps.length === 0 || currentProgressStep === undefined) {
dom.hide(progressStepsContainer)
return
}
dom.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) {
dom.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')
dom.addClass(stepEl, swalClasses['progress-step'])
dom.setInnerHtml(stepEl, step)
return stepEl
}
/**
* @param {SweetAlertOptions} params
* @returns {HTMLLIElement}
*/
const createLineElement = (params) => {
const lineEl = document.createElement('li')
dom.addClass(lineEl, swalClasses['progress-step-line'])
if (params.progressStepsDistance) {
dom.applyNumericalStyle(lineEl, 'width', params.progressStepsDistance)
}
return lineEl
}

View File

@@ -0,0 +1,27 @@
import * as dom from '../../dom/index.js'
/**
* @param {SweetAlert} instance
* @param {SweetAlertOptions} params
*/
export const renderTitle = (instance, params) => {
const title = dom.getTitle()
if (!title) {
return
}
dom.showWhenInnerHtmlPresent(title)
dom.toggle(title, params.title || params.titleText, 'block')
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,238 @@
import defaultParams from './params.js'
import { capitalizeFirstLetter, warn } from './utils.js'
const swalStringParams = ['swal-title', 'swal-html', 'swal-footer']
/**
* @param {SweetAlertOptions} params
* @returns {SweetAlertOptions}
*/
export const getTemplateParams = (params) => {
/** @type {HTMLTemplateElement} */
const template = typeof params.template === 'string' ? document.querySelector(params.template) : params.template
if (!template) {
return {}
}
/** @type {DocumentFragment} */
const templateContent = template.content
showWarningsForElements(templateContent)
const result = Object.assign(
getSwalParams(templateContent),
getSwalFunctionParams(templateContent),
getSwalButtons(templateContent),
getSwalImage(templateContent),
getSwalIcon(templateContent),
getSwalInput(templateContent),
getSwalStringParams(templateContent, swalStringParams)
)
return result
}
/**
* @param {DocumentFragment} templateContent
* @returns {SweetAlertOptions}
*/
const getSwalParams = (templateContent) => {
const result = {}
/** @type {HTMLElement[]} */
const swalParams = Array.from(templateContent.querySelectorAll('swal-param'))
swalParams.forEach((param) => {
showWarningsForAttributes(param, ['name', 'value'])
const paramName = param.getAttribute('name')
const value = param.getAttribute('value')
if (typeof defaultParams[paramName] === 'boolean') {
result[paramName] = value !== 'false'
} else if (typeof defaultParams[paramName] === 'object') {
result[paramName] = JSON.parse(value)
} else {
result[paramName] = value
}
})
return result
}
/**
* @param {DocumentFragment} templateContent
* @returns {SweetAlertOptions}
*/
const getSwalFunctionParams = (templateContent) => {
const result = {}
/** @type {HTMLElement[]} */
const swalFunctions = Array.from(templateContent.querySelectorAll('swal-function-param'))
swalFunctions.forEach((param) => {
const paramName = param.getAttribute('name')
const value = param.getAttribute('value')
result[paramName] = new Function(`return ${value}`)()
})
return result
}
/**
* @param {DocumentFragment} templateContent
* @returns {SweetAlertOptions}
*/
const getSwalButtons = (templateContent) => {
const result = {}
/** @type {HTMLElement[]} */
const swalButtons = Array.from(templateContent.querySelectorAll('swal-button'))
swalButtons.forEach((button) => {
showWarningsForAttributes(button, ['type', 'color', 'aria-label'])
const type = button.getAttribute('type')
result[`${type}ButtonText`] = button.innerHTML
result[`show${capitalizeFirstLetter(type)}Button`] = true
if (button.hasAttribute('color')) {
result[`${type}ButtonColor`] = button.getAttribute('color')
}
if (button.hasAttribute('aria-label')) {
result[`${type}ButtonAriaLabel`] = button.getAttribute('aria-label')
}
})
return result
}
/**
* @param {DocumentFragment} templateContent
* @returns {SweetAlertOptions}
*/
const getSwalImage = (templateContent) => {
const result = {}
/** @type {HTMLElement} */
const image = templateContent.querySelector('swal-image')
if (image) {
showWarningsForAttributes(image, ['src', 'width', 'height', 'alt'])
if (image.hasAttribute('src')) {
result.imageUrl = image.getAttribute('src')
}
if (image.hasAttribute('width')) {
result.imageWidth = image.getAttribute('width')
}
if (image.hasAttribute('height')) {
result.imageHeight = image.getAttribute('height')
}
if (image.hasAttribute('alt')) {
result.imageAlt = image.getAttribute('alt')
}
}
return result
}
/**
* @param {DocumentFragment} templateContent
* @returns {SweetAlertOptions}
*/
const getSwalIcon = (templateContent) => {
const result = {}
/** @type {HTMLElement} */
const icon = templateContent.querySelector('swal-icon')
if (icon) {
showWarningsForAttributes(icon, ['type', 'color'])
if (icon.hasAttribute('type')) {
/** @type {SweetAlertIcon} */
// @ts-ignore
result.icon = icon.getAttribute('type')
}
if (icon.hasAttribute('color')) {
result.iconColor = icon.getAttribute('color')
}
result.iconHtml = icon.innerHTML
}
return result
}
/**
* @param {DocumentFragment} templateContent
* @returns {SweetAlertOptions}
*/
const getSwalInput = (templateContent) => {
const result = {}
/** @type {HTMLElement} */
const input = templateContent.querySelector('swal-input')
if (input) {
showWarningsForAttributes(input, ['type', 'label', 'placeholder', 'value'])
/** @type {SweetAlertInput} */
// @ts-ignore
result.input = input.getAttribute('type') || 'text'
if (input.hasAttribute('label')) {
result.inputLabel = input.getAttribute('label')
}
if (input.hasAttribute('placeholder')) {
result.inputPlaceholder = input.getAttribute('placeholder')
}
if (input.hasAttribute('value')) {
result.inputValue = input.getAttribute('value')
}
}
/** @type {HTMLElement[]} */
const inputOptions = Array.from(templateContent.querySelectorAll('swal-input-option'))
if (inputOptions.length) {
result.inputOptions = {}
inputOptions.forEach((option) => {
showWarningsForAttributes(option, ['value'])
const optionValue = option.getAttribute('value')
const optionName = option.innerHTML
result.inputOptions[optionValue] = optionName
})
}
return result
}
/**
* @param {DocumentFragment} templateContent
* @param {string[]} paramNames
* @returns {SweetAlertOptions}
*/
const getSwalStringParams = (templateContent, paramNames) => {
const result = {}
for (const i in paramNames) {
const paramName = paramNames[i]
/** @type {HTMLElement} */
const tag = templateContent.querySelector(paramName)
if (tag) {
showWarningsForAttributes(tag, [])
result[paramName.replace(/^swal-/, '')] = tag.innerHTML.trim()
}
}
return result
}
/**
* @param {DocumentFragment} templateContent
*/
const showWarningsForElements = (templateContent) => {
const allowedElements = swalStringParams.concat([
'swal-param',
'swal-function-param',
'swal-button',
'swal-image',
'swal-icon',
'swal-input',
'swal-input-option',
])
Array.from(templateContent.children).forEach((el) => {
const tagName = el.tagName.toLowerCase()
if (!allowedElements.includes(tagName)) {
warn(`Unrecognized element <${tagName}>`)
}
})
}
/**
* @param {HTMLElement} el
* @param {string[]} allowedAttributes
*/
const showWarningsForAttributes = (el, allowedAttributes) => {
Array.from(el.attributes).forEach((attribute) => {
if (allowedAttributes.indexOf(attribute.name) === -1) {
warn([
`Unrecognized attribute "${attribute.name}" on <${el.tagName.toLowerCase()}>.`,
`${
allowedAttributes.length
? `Allowed attributes are: ${allowedAttributes.join(', ')}`
: 'To set the value, use HTML within the element.'
}`,
])
}
})
}

View File

@@ -0,0 +1,106 @@
import { swalClasses } from '../utils/classes.js'
import * as dom from './dom/index.js'
// @ts-ignore
export const isSafariOrIOS = typeof window !== 'undefined' && !!window.GestureEvent // true for Safari desktop + all iOS browsers https://stackoverflow.com/a/70585394
/**
* Fix iOS scrolling
* http://stackoverflow.com/q/39626302
*/
export const iOSfix = () => {
if (isSafariOrIOS && !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()
}
}
/**
* https://github.com/sweetalert2/sweetalert2/issues/1246
*/
const lockBodyScroll = () => {
const container = dom.getContainer()
if (!container) {
return
}
/** @type {boolean} */
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 = dom.getContainer()
const htmlContainer = dom.getHtmlContainer()
if (!container || !htmlContainer) {
return false
}
if (isStylus(event) || isZoom(event)) {
return false
}
if (target === container) {
return true
}
if (
!dom.isScrollable(container) &&
target instanceof HTMLElement &&
target.tagName !== 'INPUT' && // #1603
target.tagName !== 'TEXTAREA' && // #2266
!(
dom.isScrollable(htmlContainer) && // #1944
htmlContainer.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
}
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,6 @@
/**
* Detect Node env
*
* @returns {boolean}
*/
export const isNodeEnv = () => typeof window === 'undefined' || typeof document === 'undefined'

View File

@@ -0,0 +1,117 @@
import globalState from '../globalState.js'
import { setAriaHidden } from './aria.js'
import { swalClasses } from './classes.js'
import * as dom from './dom/index.js'
import { iOSfix } from './iosFix.js'
import { replaceScrollbarWithPadding } from './scrollbar.js'
export const SHOW_CLASS_TIMEOUT = 10
/**
* Open popup, add necessary classes and styles, fix scrollbar
*
* @param {SweetAlertOptions} params
*/
export const openPopup = (params) => {
const container = dom.getContainer()
const popup = dom.getPopup()
if (typeof params.willOpen === 'function') {
params.willOpen(popup)
}
const bodyStyles = window.getComputedStyle(document.body)
const initialBodyOverflow = bodyStyles.overflowY
addClasses(container, popup, params)
// scrolling is 'hidden' until animation is done, after that 'auto'
setTimeout(() => {
setScrollingVisibility(container, popup)
}, SHOW_CLASS_TIMEOUT)
if (dom.isModal()) {
fixScrollContainer(container, params.scrollbarPadding, initialBodyOverflow)
setAriaHidden()
}
if (!dom.isToast() && !globalState.previousActiveElement) {
globalState.previousActiveElement = document.activeElement
}
if (typeof params.didOpen === 'function') {
setTimeout(() => params.didOpen(popup))
}
dom.removeClass(container, swalClasses['no-transition'])
}
/**
* @param {AnimationEvent} event
*/
const swalOpenAnimationFinished = (event) => {
const popup = dom.getPopup()
if (event.target !== popup || !dom.animationEndEvent) {
return
}
const container = dom.getContainer()
popup.removeEventListener(dom.animationEndEvent, swalOpenAnimationFinished)
container.style.overflowY = 'auto'
}
/**
* @param {HTMLElement} container
* @param {HTMLElement} popup
*/
const setScrollingVisibility = (container, popup) => {
if (dom.animationEndEvent && dom.hasCssAnimation(popup)) {
container.style.overflowY = 'hidden'
popup.addEventListener(dom.animationEndEvent, swalOpenAnimationFinished)
} else {
container.style.overflowY = 'auto'
}
}
/**
* @param {HTMLElement} container
* @param {boolean} scrollbarPadding
* @param {string} initialBodyOverflow
*/
const fixScrollContainer = (container, scrollbarPadding, initialBodyOverflow) => {
iOSfix()
if (scrollbarPadding && initialBodyOverflow !== 'hidden') {
replaceScrollbarWithPadding(initialBodyOverflow)
}
// sweetalert2/issues/1247
setTimeout(() => {
container.scrollTop = 0
})
}
/**
* @param {HTMLElement} container
* @param {HTMLElement} popup
* @param {SweetAlertOptions} params
*/
const addClasses = (container, popup, params) => {
dom.addClass(container, params.showClass.backdrop)
if (params.animation) {
// this workaround with opacity is needed for https://github.com/sweetalert2/sweetalert2/issues/2059
popup.style.setProperty('opacity', '0', 'important')
dom.show(popup, 'grid')
setTimeout(() => {
// Animate popup right after showing it
dom.addClass(popup, params.showClass.popup)
// and remove the opacity workaround
popup.style.removeProperty('opacity')
}, SHOW_CLASS_TIMEOUT) // 10ms in order to fix #2062
} else {
dom.show(popup, 'grid')
}
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,235 @@
import { warn, warnAboutDeprecation } from '../utils/utils.js'
export const defaultParams = {
title: '',
titleText: '',
text: '',
html: '',
footer: '',
icon: undefined,
iconColor: undefined,
iconHtml: undefined,
template: 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: {},
target: 'body',
color: undefined,
backdrop: true,
heightAuto: true,
allowOutsideClick: true,
allowEscapeKey: true,
allowEnterKey: true,
stopKeydownPropagation: true,
keydownListenerCapture: false,
showConfirmButton: true,
showDenyButton: false,
showCancelButton: false,
preConfirm: undefined,
preDeny: undefined,
confirmButtonText: 'OK',
confirmButtonAriaLabel: '',
confirmButtonColor: undefined,
denyButtonText: 'No',
denyButtonAriaLabel: '',
denyButtonColor: undefined,
cancelButtonText: 'Cancel',
cancelButtonAriaLabel: '',
cancelButtonColor: undefined,
buttonsStyling: true,
reverseButtons: false,
focusConfirm: true,
focusDeny: false,
focusCancel: false,
returnFocus: true,
showCloseButton: false,
closeButtonHtml: '&times;',
closeButtonAriaLabel: 'Close this dialog',
loaderHtml: '',
showLoaderOnConfirm: false,
showLoaderOnDeny: false,
imageUrl: undefined,
imageWidth: undefined,
imageHeight: undefined,
imageAlt: '',
timer: undefined,
timerProgressBar: false,
width: undefined,
padding: undefined,
background: undefined,
input: undefined,
inputPlaceholder: '',
inputLabel: '',
inputValue: '',
inputOptions: {},
inputAutoFocus: true,
inputAutoTrim: true,
inputAttributes: {},
inputValidator: undefined,
returnInputValueOnDeny: false,
validationMessage: undefined,
grow: false,
position: 'center',
progressSteps: [],
currentProgressStep: undefined,
progressStepsDistance: undefined,
willOpen: undefined,
didOpen: undefined,
didRender: undefined,
willClose: undefined,
didClose: undefined,
didDestroy: undefined,
scrollbarPadding: true,
}
export const updatableParams = [
'allowEscapeKey',
'allowOutsideClick',
'background',
'buttonsStyling',
'cancelButtonAriaLabel',
'cancelButtonColor',
'cancelButtonText',
'closeButtonAriaLabel',
'closeButtonHtml',
'color',
'confirmButtonAriaLabel',
'confirmButtonColor',
'confirmButtonText',
'currentProgressStep',
'customClass',
'denyButtonAriaLabel',
'denyButtonColor',
'denyButtonText',
'didClose',
'didDestroy',
'footer',
'hideClass',
'html',
'icon',
'iconColor',
'iconHtml',
'imageAlt',
'imageHeight',
'imageUrl',
'imageWidth',
'preConfirm',
'preDeny',
'progressSteps',
'returnFocus',
'reverseButtons',
'showCancelButton',
'showCloseButton',
'showConfirmButton',
'showDenyButton',
'text',
'title',
'titleText',
'willClose',
]
/** @type {Record<string, string>} */
export const deprecatedParams = {}
const toastIncompatibleParams = [
'allowOutsideClick',
'allowEnterKey',
'backdrop',
'focusConfirm',
'focusDeny',
'focusCancel',
'returnFocus',
'heightAuto',
'keydownListenerCapture',
]
/**
* Is valid parameter
*
* @param {string} paramName
* @returns {boolean}
*/
export const isValidParameter = (paramName) => {
return Object.prototype.hasOwnProperty.call(defaultParams, paramName)
}
/**
* Is valid parameter for Swal.update() method
*
* @param {string} paramName
* @returns {boolean}
*/
export const isUpdatableParameter = (paramName) => {
return updatableParams.indexOf(paramName) !== -1
}
/**
* Is deprecated parameter
*
* @param {string} paramName
* @returns {string | undefined}
*/
export const isDeprecatedParameter = (paramName) => {
return deprecatedParams[paramName]
}
/**
* @param {string} param
*/
const checkIfParamIsValid = (param) => {
if (!isValidParameter(param)) {
warn(`Unknown parameter "${param}"`)
}
}
/**
* @param {string} param
*/
const checkIfToastParamIsValid = (param) => {
if (toastIncompatibleParams.includes(param)) {
warn(`The parameter "${param}" is incompatible with toasts`)
}
}
/**
* @param {string} param
*/
const checkIfParamIsDeprecated = (param) => {
const isDeprecated = isDeprecatedParameter(param)
if (isDeprecated) {
warnAboutDeprecation(param, isDeprecated)
}
}
/**
* Show relevant warnings for given params
*
* @param {SweetAlertOptions} params
*/
export const showWarningsForParams = (params) => {
if (params.backdrop === false && params.allowOutsideClick) {
warn('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`')
}
for (const param in params) {
checkIfParamIsValid(param)
if (params.toast) {
checkIfToastParamIsValid(param)
}
checkIfParamIsDeprecated(param)
}
}
export default defaultParams

View File

@@ -0,0 +1,48 @@
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
*
* @returns {number}
*/
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
}
/**
* Remember state in cases where opening and handling a modal will fiddle with it.
* @type {number | null}
*/
let previousBodyPadding = null
/**
* @param {string} initialBodyOverflow
*/
export const replaceScrollbarWithPadding = (initialBodyOverflow) => {
// for queues, do not do this more than once
if (previousBodyPadding !== null) {
return
}
// if the body has overflow
if (
document.body.scrollHeight > window.innerHeight ||
initialBodyOverflow === 'scroll' // https://github.com/sweetalert2/sweetalert2/issues/2663
) {
// 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`
}
}
export const undoReplaceScrollbarWithPadding = () => {
if (previousBodyPadding !== null) {
document.body.style.paddingRight = `${previousBodyPadding}px`
previousBodyPadding = null
}
}

View File

@@ -0,0 +1,61 @@
import defaultInputValidators from './defaultInputValidators.js'
import * as dom from './dom/index.js'
import { warn } from './utils.js'
/**
* @param {SweetAlertOptions} params
*/
function setDefaultInputValidators(params) {
// Use default `inputValidator` for supported input types if not provided
if (params.inputValidator) {
return
}
if (params.input === 'email') {
params.inputValidator = defaultInputValidators['email']
}
if (params.input === 'url') {
params.inputValidator = defaultInputValidators['url']
}
}
/**
* @param {SweetAlertOptions} params
*/
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 {SweetAlertOptions} params
*/
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'
)
}
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,86 @@
export const consolePrefix = 'SweetAlert2:'
/**
* Capitalize the first letter of a string
*
* @param {string} str
* @returns {string}
*/
export const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1)
/**
* Standardize console warnings
*
* @param {string | string[]} message
*/
export const warn = (message) => {
console.warn(`${consolePrefix} ${typeof message === 'object' ? message.join(' ') : message}`)
}
/**
* Standardize console errors
*
* @param {string} message
*/
export 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
*/
export 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
*/
export 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}
*/
export const callIfFunction = (arg) => (typeof arg === 'function' ? arg() : arg)
/**
* @param {any} arg
* @returns {boolean}
*/
export const hasToPromiseFn = (arg) => arg && typeof arg.toPromise === 'function'
/**
* @param {any} arg
* @returns {Promise<any>}
*/
export const asPromise = (arg) => (hasToPromiseFn(arg) ? arg.toPromise() : Promise.resolve(arg))
/**
* @param {any} arg
* @returns {boolean}
*/
export const isPromise = (arg) => arg && Promise.resolve(arg) === arg

View File

@@ -0,0 +1,265 @@
$swal2-white: #fff !default;
$swal2-black: #000 !default;
$swal2-outline-color: rgba(100, 150, 200, 0.5) !default;
// CONTAINER
$swal2-container-padding: 0.625em !default;
// POPUP
$swal2-width: 32em !default;
$swal2-padding: 0 0 1.25em !default;
$swal2-border: none !default;
$swal2-color: lighten($swal2-black, 33) !default;
$swal2-border-radius: 5px !default;
$swal2-box-shadow: #d9d9d9 !default;
// ANIMATIONS
$swal2-show-animation: swal2-show 0.3s !default;
$swal2-hide-animation: swal2-hide 0.15s forwards !default;
// BACKGROUND
$swal2-background: $swal2-white !default;
// TYPOGRAPHY
$swal2-font: inherit !default;
$swal2-font-size: 1rem !default;
// BACKDROP
$swal2-backdrop: rgba($swal2-black, 0.4) !default;
$swal2-backdrop-transition: background-color 0.1s !default;
// ICONS
$swal2-icon-size: 5em !default;
$swal2-icon-animations: true !default;
$swal2-icon-margin: 2.5em auto 0.6em !default;
$swal2-icon-font-family: inherit !default;
$swal2-icon-font-size: 3.75em !default;
$swal2-icon-border-color: #000 !default;
$swal2-icon-zoom: null !default;
$swal2-success: #a5dc86 !default;
$swal2-success-border: rgba($swal2-success, 0.3) !default;
$swal2-error: #f27474 !default;
$swal2-warning: #f8bb86 !default;
$swal2-info: #3fc3ee !default;
$swal2-question: #87adbd !default;
// IMAGE
$swal2-image-margin: 2em auto 1em !default;
// TITLE
$swal2-title-position: relative !default;
$swal2-title-max-width: 100% !default;
$swal2-title-margin: 0 !default;
$swal2-title-padding: 0.8em 1em 0 !default;
$swal2-title-color: inherit !default;
$swal2-title-font-size: 1.875em !default;
$swal2-title-font-weight: 600 !default;
$swal2-title-text-align: center !default;
// HTML CONTAINER
$swal2-html-container-justify-content: center !default;
$swal2-html-container-margin: 1em 1.6em 0.3em !default;
$swal2-html-container-padding: 0 !default;
$swal2-html-container-overflow: auto !default;
$swal2-html-container-color: inherit !default;
$swal2-html-container-font-size: 1.125em !default;
$swal2-html-container-font-weight: normal !default;
$swal2-html-container-line-height: normal !default;
$swal2-html-container-text-align: center !default;
$swal2-html-container-word-wrap: break-word !default;
$swal2-html-container-word-break: break-word !default;
// INPUT
$swal2-input-margin: 1em 2em 3px !default;
$swal2-input-width: auto !default;
$swal2-input-height: 2.625em !default;
$swal2-input-padding: 0 0.75em !default;
$swal2-input-border: 1px solid lighten($swal2-black, 85) !default;
$swal2-input-border-radius: 0.1875em !default;
$swal2-input-box-shadow:
inset 0 1px 1px rgba($swal2-black, 0.06),
0 0 0 3px transparent !default;
$swal2-input-font-size: 1.125em !default;
$swal2-input-background: transparent !default;
$swal2-input-color: inherit !default;
$swal2-input-transition:
border-color 0.1s,
box-shadow 0.1s !default;
// INPUT:FOCUS
$swal2-input-focus-border: 1px solid #b4dbed !default;
$swal2-input-focus-outline: none !default;
$swal2-input-focus-box-shadow:
inset 0 1px 1px rgba($swal2-black, 0.06),
0 0 0 3px $swal2-outline-color !default;
// TEXTAREA SPECIFIC VARIABLES
$swal2-textarea-height: 6.75em !default;
$swal2-textarea-padding: 0.75em !default;
// INPUT LABEL
$swal2-input-label-margin: 1em auto 0 !default;
$swal2-input-label-justify-content: center !default;
// VALIDATION MESSAGE
$swal2-validation-message-align-items: center !default;
$swal2-validation-message-justify-content: center !default;
$swal2-validation-message-margin: 1em 0 0 !default;
$swal2-validation-message-padding: 0.625em !default;
$swal2-validation-message-background: lighten($swal2-black, 94) !default;
$swal2-validation-message-color: lighten($swal2-black, 40) !default;
$swal2-validation-message-font-size: 1em !default;
$swal2-validation-message-font-weight: 300 !default;
$swal2-validation-message-icon-background: $swal2-error !default;
$swal2-validation-message-icon-color: $swal2-white !default;
$swal2-validation-message-icon-zoom: null !default;
// PROGRESS STEPS
$swal2-progress-steps-flex-wrap: wrap !default;
$swal2-progress-steps-align-items: center !default;
$swal2-progress-steps-max-width: 100% !default;
$swal2-progress-steps-background: transparent !default;
$swal2-progress-steps-margin: 1.25em auto !default;
$swal2-progress-steps-padding: 0 !default;
$swal2-progress-steps-font-weight: 600 !default;
$swal2-progress-steps-distance: 2.5em !default;
$swal2-progress-step-width: 2em;
$swal2-progress-step-height: 2em;
$swal2-progress-step-border-radius: 2em;
$swal2-progress-step-background: #add8e6 !default;
$swal2-progress-step-color: $swal2-white !default;
$swal2-active-step-background: #2778c4 !default;
$swal2-active-step-color: $swal2-white !default;
// FOOTER
$swal2-footer-margin: 1em 0 0 !default;
$swal2-footer-padding: 1em 1em 0 !default;
$swal2-footer-border-color: #eee !default;
$swal2-footer-color: inherit !default;
$swal2-footer-font-size: 1em !default;
$swal2-footer-text-align: center !default;
// TIMER PROGRESS BAR
$swal2-timer-progress-bar-height: 0.25em;
$swal2-timer-progress-bar-background: rgba($swal2-black, 0.2) !default;
// CLOSE BUTTON
$swal2-close-button-justify-self: end !default;
$swal2-close-button-align-items: center !default;
$swal2-close-button-justify-content: center !default;
$swal2-close-button-width: 1.2em !default;
$swal2-close-button-height: 1.2em !default;
$swal2-close-button-position: null !default;
$swal2-close-button-gap: 0 !default;
$swal2-close-button-padding: 0 !default;
$swal2-close-button-transition:
color 0.1s,
box-shadow 0.1s !default;
$swal2-close-button-border: none !default;
$swal2-close-button-border-radius: $swal2-border-radius !default;
$swal2-close-button-outline: null !default;
$swal2-close-button-background: transparent !default;
$swal2-close-button-color: #ccc !default;
$swal2-close-button-font-family: monospace !default;
$swal2-close-button-font-size: 2.5em !default;
$swal2-close-button-box-shadow: inset 0 0 0 3px transparent !default;
// CLOSE BUTTON:HOVER
$swal2-close-button-hover-transform: none !default;
$swal2-close-button-hover-color: $swal2-error !default;
$swal2-close-button-hover-background: transparent !default;
// CLOSE BUTTON:FOCUS
$swal2-close-button-focus-outline: none !default;
$swal2-close-button-focus-box-shadow: inset 0 0 0 3px $swal2-outline-color !default;
// ACTIONS
$swal2-actions-flex-wrap: wrap !default;
$swal2-actions-align-items: center !default;
$swal2-actions-justify-content: center !default;
$swal2-actions-width: auto !default;
$swal2-actions-margin: 1.25em auto 0 !default;
$swal2-actions-padding: 0 !default;
// COMMON VARIABLES FOR ALL ACTION BUTTONS
$swal2-button-margin: 0.3125em !default;
$swal2-button-padding: 0.625em 1.1em !default;
$swal2-button-transition: box-shadow 0.1s !default;
$swal2-button-box-shadow: 0 0 0 3px transparent !default;
$swal2-button-font-weight: 500 !default;
$swal2-button-darken-hover: rgba($swal2-black, 0.1) !default;
$swal2-button-darken-active: rgba($swal2-black, 0.2) !default;
$swal2-button-focus-outline: none !default;
$swal2-button-focus-box-shadow: 0 0 0 3px $swal2-outline-color !default;
// CONFIRM BUTTON
$swal2-confirm-button-order: null !default;
$swal2-confirm-button-border: 0 !default;
$swal2-confirm-button-border-radius: 0.25em !default;
$swal2-confirm-button-background-color: #7066e0 !default;
$swal2-confirm-button-color: $swal2-white !default;
$swal2-confirm-button-font-size: 1em !default;
$swal2-confirm-button-focus-box-shadow: 0 0 0 3px rgba($swal2-confirm-button-background-color, 0.5) !default;
// DENY BUTTON
$swal2-deny-button-order: null !default;
$swal2-deny-button-border: 0 !default;
$swal2-deny-button-border-radius: 0.25em !default;
$swal2-deny-button-background-color: #dc3741 !default;
$swal2-deny-button-color: $swal2-white !default;
$swal2-deny-button-font-size: 1em !default;
$swal2-deny-button-focus-box-shadow: 0 0 0 3px rgba($swal2-deny-button-background-color, 0.5) !default;
// CANCEL BUTTON
$swal2-cancel-button-order: null !default;
$swal2-cancel-button-border: 0 !default;
$swal2-cancel-button-border-radius: 0.25em !default;
$swal2-cancel-button-background-color: #6e7881 !default;
$swal2-cancel-button-color: $swal2-white !default;
$swal2-cancel-button-font-size: 1em !default;
$swal2-cancel-button-focus-box-shadow: 0 0 0 3px rgba($swal2-cancel-button-background-color, 0.5) !default;
// LOADER
$swal2-loader-align-items: center !default;
$swal2-loader-justify-content: center !default;
$swal2-loader-width: 2.2em !default;
$swal2-loader-height: 2.2em !default;
$swal2-loader-margin: 0 1.875em !default;
$swal2-loader-animation: swal2-rotate-loading 1.5s linear 0s infinite normal !default;
$swal2-loader-border-width: 0.25em !default;
$swal2-loader-border-style: solid !default;
$swal2-loader-border-radius: 100% !default;
$swal2-loader-border-color: #2778c4 transparent #2778c4 transparent !default;
// TOASTS
$swal2-toast-show-animation: swal2-toast-show 0.5s !default;
$swal2-toast-hide-animation: swal2-toast-hide 0.1s forwards !default;
$swal2-toast-border: none !default;
$swal2-toast-box-shadow:
0 0 1px hsl(0deg 0% 0% / 0.075),
0 1px 2px hsl(0deg 0% 0% / 0.075),
1px 2px 4px hsl(0deg 0% 0% / 0.075),
1px 3px 8px hsl(0deg 0% 0% / 0.075),
2px 4px 16px hsl(0deg 0% 0% / 0.075) !default;
$swal2-toast-background: $swal2-white !default;
$swal2-toast-close-button-width: 0.8em !default;
$swal2-toast-close-button-height: 0.8em !default;
$swal2-toast-close-button-margin: 0 !default;
$swal2-toast-close-button-font-size: 2em !default;
$swal2-toast-width: 360px !default;
$swal2-toast-padding: 1em !default;
$swal2-toast-title-margin: 0.5em 1em !default;
$swal2-toast-title-padding: 0 !default;
$swal2-toast-title-font-size: 1em !default;
$swal2-toast-icon-font-size: 1.8em !default;
$swal2-toast-html-container-margin: 0.5em 1em !default;
$swal2-toast-html-container-padding: 0 !default;
$swal2-toast-html-container-font-size: 1em !default;
$swal2-toast-input-height: 2em !default;
$swal2-toast-input-margin: 0.5em !default;
$swal2-toast-input-font-size: 1em !default;
$swal2-toast-validation-font-size: 1em !default;
$swal2-toast-buttons-font-size: 1em !default;
$swal2-toast-footer-margin: 0.5em 0 0 !default;
$swal2-toast-footer-padding: 0.5em 0 0 !default;
$swal2-toast-footer-font-size: 0.8em !default;

File diff suppressed because it is too large Load Diff