package.src.keydown-handler.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sweetalert2 Show documentation
Show all versions of sweetalert2 Show documentation
A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes, supported fork of sweetalert
The newest version!
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)
}
}