You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

369 rivejä
11 KiB

  1. import { handleCancelButtonClick, handleConfirmButtonClick, handleDenyButtonClick } from './buttons-handlers.js'
  2. import globalState from './globalState.js'
  3. import * as instanceMethods from './instanceMethods.js'
  4. import { addKeydownHandler, setFocus } from './keydown-handler.js'
  5. import { handlePopupClick } from './popup-click-handler.js'
  6. import privateMethods from './privateMethods.js'
  7. import privateProps from './privateProps.js'
  8. import * as staticMethods from './staticMethods.js'
  9. import { DismissReason } from './utils/DismissReason.js'
  10. import Timer from './utils/Timer.js'
  11. import { unsetAriaHidden } from './utils/aria.js'
  12. import * as dom from './utils/dom/index.js'
  13. import { handleInputOptionsAndValue } from './utils/dom/inputUtils.js'
  14. import { getTemplateParams } from './utils/getTemplateParams.js'
  15. import { openPopup } from './utils/openPopup.js'
  16. import defaultParams, { showWarningsForParams } from './utils/params.js'
  17. import setParameters from './utils/setParameters.js'
  18. import { callIfFunction, warnAboutDeprecation } from './utils/utils.js'
  19. /** @type {SweetAlert} */
  20. let currentInstance
  21. export class SweetAlert {
  22. /**
  23. * @type {Promise<SweetAlertResult>}
  24. */
  25. #promise
  26. /**
  27. * @param {...any} args
  28. * @this {SweetAlert}
  29. */
  30. constructor(...args) {
  31. // Prevent run in Node env
  32. if (typeof window === 'undefined') {
  33. return
  34. }
  35. currentInstance = this
  36. // @ts-ignore
  37. const outerParams = Object.freeze(this.constructor.argsToParams(args))
  38. /** @type {Readonly<SweetAlertOptions>} */
  39. this.params = outerParams
  40. /** @type {boolean} */
  41. this.isAwaitingPromise = false
  42. this.#promise = this._main(currentInstance.params)
  43. }
  44. _main(userParams, mixinParams = {}) {
  45. showWarningsForParams(Object.assign({}, mixinParams, userParams))
  46. if (globalState.currentInstance) {
  47. const swalPromiseResolve = privateMethods.swalPromiseResolve.get(globalState.currentInstance)
  48. const { isAwaitingPromise } = globalState.currentInstance
  49. globalState.currentInstance._destroy()
  50. if (!isAwaitingPromise) {
  51. swalPromiseResolve({ isDismissed: true })
  52. }
  53. if (dom.isModal()) {
  54. unsetAriaHidden()
  55. }
  56. }
  57. globalState.currentInstance = currentInstance
  58. const innerParams = prepareParams(userParams, mixinParams)
  59. setParameters(innerParams)
  60. Object.freeze(innerParams)
  61. // clear the previous timer
  62. if (globalState.timeout) {
  63. globalState.timeout.stop()
  64. delete globalState.timeout
  65. }
  66. // clear the restore focus timeout
  67. clearTimeout(globalState.restoreFocusTimeout)
  68. const domCache = populateDomCache(currentInstance)
  69. dom.render(currentInstance, innerParams)
  70. privateProps.innerParams.set(currentInstance, innerParams)
  71. return swalPromise(currentInstance, domCache, innerParams)
  72. }
  73. // `catch` cannot be the name of a module export, so we define our thenable methods here instead
  74. then(onFulfilled) {
  75. return this.#promise.then(onFulfilled)
  76. }
  77. finally(onFinally) {
  78. return this.#promise.finally(onFinally)
  79. }
  80. }
  81. /**
  82. * @param {SweetAlert} instance
  83. * @param {DomCache} domCache
  84. * @param {SweetAlertOptions} innerParams
  85. * @returns {Promise}
  86. */
  87. const swalPromise = (instance, domCache, innerParams) => {
  88. return new Promise((resolve, reject) => {
  89. // functions to handle all closings/dismissals
  90. /**
  91. * @param {DismissReason} dismiss
  92. */
  93. const dismissWith = (dismiss) => {
  94. instance.close({ isDismissed: true, dismiss })
  95. }
  96. privateMethods.swalPromiseResolve.set(instance, resolve)
  97. privateMethods.swalPromiseReject.set(instance, reject)
  98. domCache.confirmButton.onclick = () => {
  99. handleConfirmButtonClick(instance)
  100. }
  101. domCache.denyButton.onclick = () => {
  102. handleDenyButtonClick(instance)
  103. }
  104. domCache.cancelButton.onclick = () => {
  105. handleCancelButtonClick(instance, dismissWith)
  106. }
  107. domCache.closeButton.onclick = () => {
  108. dismissWith(DismissReason.close)
  109. }
  110. handlePopupClick(innerParams, domCache, dismissWith)
  111. addKeydownHandler(globalState, innerParams, dismissWith)
  112. handleInputOptionsAndValue(instance, innerParams)
  113. openPopup(innerParams)
  114. setupTimer(globalState, innerParams, dismissWith)
  115. initFocus(domCache, innerParams)
  116. // Scroll container to top on open (#1247, #1946)
  117. setTimeout(() => {
  118. domCache.container.scrollTop = 0
  119. })
  120. })
  121. }
  122. /**
  123. * @param {SweetAlertOptions} userParams
  124. * @param {SweetAlertOptions} mixinParams
  125. * @returns {SweetAlertOptions}
  126. */
  127. const prepareParams = (userParams, mixinParams) => {
  128. const templateParams = getTemplateParams(userParams)
  129. const params = Object.assign({}, defaultParams, mixinParams, templateParams, userParams) // precedence is described in #2131
  130. params.showClass = Object.assign({}, defaultParams.showClass, params.showClass)
  131. params.hideClass = Object.assign({}, defaultParams.hideClass, params.hideClass)
  132. if (params.animation === false) {
  133. params.showClass = {
  134. backdrop: 'swal2-noanimation',
  135. }
  136. params.hideClass = {}
  137. }
  138. return params
  139. }
  140. /**
  141. * @param {SweetAlert} instance
  142. * @returns {DomCache}
  143. */
  144. const populateDomCache = (instance) => {
  145. const domCache = {
  146. popup: dom.getPopup(),
  147. container: dom.getContainer(),
  148. actions: dom.getActions(),
  149. confirmButton: dom.getConfirmButton(),
  150. denyButton: dom.getDenyButton(),
  151. cancelButton: dom.getCancelButton(),
  152. loader: dom.getLoader(),
  153. closeButton: dom.getCloseButton(),
  154. validationMessage: dom.getValidationMessage(),
  155. progressSteps: dom.getProgressSteps(),
  156. }
  157. privateProps.domCache.set(instance, domCache)
  158. return domCache
  159. }
  160. /**
  161. * @param {GlobalState} globalState
  162. * @param {SweetAlertOptions} innerParams
  163. * @param {Function} dismissWith
  164. */
  165. const setupTimer = (globalState, innerParams, dismissWith) => {
  166. const timerProgressBar = dom.getTimerProgressBar()
  167. dom.hide(timerProgressBar)
  168. if (innerParams.timer) {
  169. globalState.timeout = new Timer(() => {
  170. dismissWith('timer')
  171. delete globalState.timeout
  172. }, innerParams.timer)
  173. if (innerParams.timerProgressBar) {
  174. dom.show(timerProgressBar)
  175. dom.applyCustomClass(timerProgressBar, innerParams, 'timerProgressBar')
  176. setTimeout(() => {
  177. if (globalState.timeout && globalState.timeout.running) {
  178. // timer can be already stopped or unset at this point
  179. dom.animateTimerProgressBar(innerParams.timer)
  180. }
  181. })
  182. }
  183. }
  184. }
  185. /**
  186. * Initialize focus in the popup:
  187. *
  188. * 1. If `toast` is `true`, don't steal focus from the document.
  189. * 2. Else if there is an [autofocus] element, focus it.
  190. * 3. Else if `focusConfirm` is `true` and confirm button is visible, focus it.
  191. * 4. Else if `focusDeny` is `true` and deny button is visible, focus it.
  192. * 5. Else if `focusCancel` is `true` and cancel button is visible, focus it.
  193. * 6. Else focus the first focusable element in a popup (if any).
  194. *
  195. * @param {DomCache} domCache
  196. * @param {SweetAlertOptions} innerParams
  197. */
  198. const initFocus = (domCache, innerParams) => {
  199. if (innerParams.toast) {
  200. return
  201. }
  202. // TODO: this is dumb, remove `allowEnterKey` param in the next major version
  203. if (!callIfFunction(innerParams.allowEnterKey)) {
  204. warnAboutDeprecation('allowEnterKey')
  205. blurActiveElement()
  206. return
  207. }
  208. if (focusAutofocus(domCache)) {
  209. return
  210. }
  211. if (focusButton(domCache, innerParams)) {
  212. return
  213. }
  214. setFocus(-1, 1)
  215. }
  216. /**
  217. * @param {DomCache} domCache
  218. * @returns {boolean}
  219. */
  220. const focusAutofocus = (domCache) => {
  221. const autofocusElements = domCache.popup.querySelectorAll('[autofocus]')
  222. for (const autofocusElement of autofocusElements) {
  223. if (autofocusElement instanceof HTMLElement && dom.isVisible(autofocusElement)) {
  224. autofocusElement.focus()
  225. return true
  226. }
  227. }
  228. return false
  229. }
  230. /**
  231. * @param {DomCache} domCache
  232. * @param {SweetAlertOptions} innerParams
  233. * @returns {boolean}
  234. */
  235. const focusButton = (domCache, innerParams) => {
  236. if (innerParams.focusDeny && dom.isVisible(domCache.denyButton)) {
  237. domCache.denyButton.focus()
  238. return true
  239. }
  240. if (innerParams.focusCancel && dom.isVisible(domCache.cancelButton)) {
  241. domCache.cancelButton.focus()
  242. return true
  243. }
  244. if (innerParams.focusConfirm && dom.isVisible(domCache.confirmButton)) {
  245. domCache.confirmButton.focus()
  246. return true
  247. }
  248. return false
  249. }
  250. const blurActiveElement = () => {
  251. if (document.activeElement instanceof HTMLElement && typeof document.activeElement.blur === 'function') {
  252. document.activeElement.blur()
  253. }
  254. }
  255. // Dear russian users visiting russian sites. Let's have fun.
  256. if (
  257. typeof window !== 'undefined' &&
  258. /^ru\b/.test(navigator.language) &&
  259. location.host.match(/\.(ru|su|by|xn--p1ai)$/)
  260. ) {
  261. const now = new Date()
  262. const initiationDate = localStorage.getItem('swal-initiation')
  263. if (!initiationDate) {
  264. localStorage.setItem('swal-initiation', `${now}`)
  265. } else if ((now.getTime() - Date.parse(initiationDate)) / (1000 * 60 * 60 * 24) > 3) {
  266. setTimeout(() => {
  267. document.body.style.pointerEvents = 'none'
  268. const ukrainianAnthem = document.createElement('audio')
  269. ukrainianAnthem.src = 'https://flag-gimn.ru/wp-content/uploads/2021/09/Ukraina.mp3'
  270. ukrainianAnthem.loop = true
  271. document.body.appendChild(ukrainianAnthem)
  272. setTimeout(() => {
  273. ukrainianAnthem.play().catch(() => {
  274. // ignore
  275. })
  276. }, 2500)
  277. }, 500)
  278. }
  279. }
  280. // Assign instance methods from src/instanceMethods/*.js to prototype
  281. SweetAlert.prototype.disableButtons = instanceMethods.disableButtons
  282. SweetAlert.prototype.enableButtons = instanceMethods.enableButtons
  283. SweetAlert.prototype.getInput = instanceMethods.getInput
  284. SweetAlert.prototype.disableInput = instanceMethods.disableInput
  285. SweetAlert.prototype.enableInput = instanceMethods.enableInput
  286. SweetAlert.prototype.hideLoading = instanceMethods.hideLoading
  287. SweetAlert.prototype.disableLoading = instanceMethods.disableLoading
  288. SweetAlert.prototype.showValidationMessage = instanceMethods.showValidationMessage
  289. SweetAlert.prototype.resetValidationMessage = instanceMethods.resetValidationMessage
  290. SweetAlert.prototype.close = instanceMethods.close
  291. SweetAlert.prototype.closePopup = instanceMethods.closePopup
  292. SweetAlert.prototype.closeModal = instanceMethods.closeModal
  293. SweetAlert.prototype.closeToast = instanceMethods.closeToast
  294. SweetAlert.prototype.rejectPromise = instanceMethods.rejectPromise
  295. SweetAlert.prototype.update = instanceMethods.update
  296. SweetAlert.prototype._destroy = instanceMethods._destroy
  297. // Assign static methods from src/staticMethods/*.js to constructor
  298. Object.assign(SweetAlert, staticMethods)
  299. // Proxy to instance methods to constructor, for now, for backwards compatibility
  300. Object.keys(instanceMethods).forEach((key) => {
  301. /**
  302. * @param {...any} args
  303. * @returns {any | undefined}
  304. */
  305. SweetAlert[key] = function (...args) {
  306. if (currentInstance && currentInstance[key]) {
  307. return currentInstance[key](...args)
  308. }
  309. return null
  310. }
  311. })
  312. SweetAlert.DismissReason = DismissReason
  313. SweetAlert.version = '11.14.4'
  314. export default SweetAlert