Spaces:
Running
Running
/** | |
* -------------------------------------------------------------------------- | |
* Bootstrap (v5.1.3): dom/event-handler.js | |
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |
* -------------------------------------------------------------------------- | |
*/ | |
import { | |
getjQuery | |
} from '../util/index' | |
/** | |
* ------------------------------------------------------------------------ | |
* Constants | |
* ------------------------------------------------------------------------ | |
*/ | |
const namespaceRegex = /[^.]*(?=\..*)\.|.*/ | |
const stripNameRegex = /\..*/ | |
const stripUidRegex = /::\d+$/ | |
const eventRegistry = {} // Events storage | |
let uidEvent = 1 | |
const customEvents = { | |
mouseenter: 'mouseover', | |
mouseleave: 'mouseout' | |
} | |
const customEventsRegex = /^(mouseenter|mouseleave)/i | |
const nativeEvents = new Set([ | |
'click', | |
'dblclick', | |
'mouseup', | |
'mousedown', | |
'contextmenu', | |
'mousewheel', | |
'DOMMouseScroll', | |
'mouseover', | |
'mouseout', | |
'mousemove', | |
'selectstart', | |
'selectend', | |
'keydown', | |
'keypress', | |
'keyup', | |
'orientationchange', | |
'touchstart', | |
'touchmove', | |
'touchend', | |
'touchcancel', | |
'pointerdown', | |
'pointermove', | |
'pointerup', | |
'pointerleave', | |
'pointercancel', | |
'gesturestart', | |
'gesturechange', | |
'gestureend', | |
'focus', | |
'blur', | |
'change', | |
'reset', | |
'select', | |
'submit', | |
'focusin', | |
'focusout', | |
'load', | |
'unload', | |
'beforeunload', | |
'resize', | |
'move', | |
'DOMContentLoaded', | |
'readystatechange', | |
'error', | |
'abort', | |
'scroll' | |
]) | |
/** | |
* ------------------------------------------------------------------------ | |
* Private methods | |
* ------------------------------------------------------------------------ | |
*/ | |
function getUidEvent(element, uid) { | |
return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++ | |
} | |
function getEvent(element) { | |
const uid = getUidEvent(element) | |
element.uidEvent = uid | |
eventRegistry[uid] = eventRegistry[uid] || {} | |
return eventRegistry[uid] | |
} | |
function bootstrapHandler(element, fn) { | |
return function handler(event) { | |
event.delegateTarget = element | |
if (handler.oneOff) { | |
EventHandler.off(element, event.type, fn) | |
} | |
return fn.apply(element, [event]) | |
} | |
} | |
function bootstrapDelegationHandler(element, selector, fn) { | |
return function handler(event) { | |
const domElements = element.querySelectorAll(selector) | |
for (let { | |
target | |
} = event; target && target !== this; target = target.parentNode) { | |
for (let i = domElements.length; i--;) { | |
if (domElements[i] === target) { | |
event.delegateTarget = target | |
if (handler.oneOff) { | |
EventHandler.off(element, event.type, selector, fn) | |
} | |
return fn.apply(target, [event]) | |
} | |
} | |
} | |
// To please ESLint | |
return null | |
} | |
} | |
function findHandler(events, handler, delegationSelector = null) { | |
const uidEventList = Object.keys(events) | |
for (let i = 0, len = uidEventList.length; i < len; i++) { | |
const event = events[uidEventList[i]] | |
if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { | |
return event | |
} | |
} | |
return null | |
} | |
function normalizeParams(originalTypeEvent, handler, delegationFn) { | |
const delegation = typeof handler === 'string' | |
const originalHandler = delegation ? delegationFn : handler | |
let typeEvent = getTypeEvent(originalTypeEvent) | |
const isNative = nativeEvents.has(typeEvent) | |
if (!isNative) { | |
typeEvent = originalTypeEvent | |
} | |
return [delegation, originalHandler, typeEvent] | |
} | |
function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { | |
if (typeof originalTypeEvent !== 'string' || !element) { | |
return | |
} | |
if (!handler) { | |
handler = delegationFn | |
delegationFn = null | |
} | |
// in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position | |
// this prevents the handler from being dispatched the same way as mouseover or mouseout does | |
if (customEventsRegex.test(originalTypeEvent)) { | |
const wrapFn = fn => { | |
return function(event) { | |
if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) { | |
return fn.call(this, event) | |
} | |
} | |
} | |
if (delegationFn) { | |
delegationFn = wrapFn(delegationFn) | |
} else { | |
handler = wrapFn(handler) | |
} | |
} | |
const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) | |
const events = getEvent(element) | |
const handlers = events[typeEvent] || (events[typeEvent] = {}) | |
const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null) | |
if (previousFn) { | |
previousFn.oneOff = previousFn.oneOff && oneOff | |
return | |
} | |
const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) | |
const fn = delegation ? | |
bootstrapDelegationHandler(element, handler, delegationFn) : | |
bootstrapHandler(element, handler) | |
fn.delegationSelector = delegation ? handler : null | |
fn.originalHandler = originalHandler | |
fn.oneOff = oneOff | |
fn.uidEvent = uid | |
handlers[uid] = fn | |
element.addEventListener(typeEvent, fn, delegation) | |
} | |
function removeHandler(element, events, typeEvent, handler, delegationSelector) { | |
const fn = findHandler(events[typeEvent], handler, delegationSelector) | |
if (!fn) { | |
return | |
} | |
element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)) | |
delete events[typeEvent][fn.uidEvent] | |
} | |
function removeNamespacedHandlers(element, events, typeEvent, namespace) { | |
const storeElementEvent = events[typeEvent] || {} | |
Object.keys(storeElementEvent).forEach(handlerKey => { | |
if (handlerKey.includes(namespace)) { | |
const event = storeElementEvent[handlerKey] | |
removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) | |
} | |
}) | |
} | |
function getTypeEvent(event) { | |
// allow to get the native events from namespaced events ('click.bs.button' --> 'click') | |
event = event.replace(stripNameRegex, '') | |
return customEvents[event] || event | |
} | |
const EventHandler = { | |
on(element, event, handler, delegationFn) { | |
addHandler(element, event, handler, delegationFn, false) | |
}, | |
one(element, event, handler, delegationFn) { | |
addHandler(element, event, handler, delegationFn, true) | |
}, | |
off(element, originalTypeEvent, handler, delegationFn) { | |
if (typeof originalTypeEvent !== 'string' || !element) { | |
return | |
} | |
const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) | |
const inNamespace = typeEvent !== originalTypeEvent | |
const events = getEvent(element) | |
const isNamespace = originalTypeEvent.startsWith('.') | |
if (typeof originalHandler !== 'undefined') { | |
// Simplest case: handler is passed, remove that listener ONLY. | |
if (!events || !events[typeEvent]) { | |
return | |
} | |
removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null) | |
return | |
} | |
if (isNamespace) { | |
Object.keys(events).forEach(elementEvent => { | |
removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)) | |
}) | |
} | |
const storeElementEvent = events[typeEvent] || {} | |
Object.keys(storeElementEvent).forEach(keyHandlers => { | |
const handlerKey = keyHandlers.replace(stripUidRegex, '') | |
if (!inNamespace || originalTypeEvent.includes(handlerKey)) { | |
const event = storeElementEvent[keyHandlers] | |
removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) | |
} | |
}) | |
}, | |
trigger(element, event, args) { | |
if (typeof event !== 'string' || !element) { | |
return null | |
} | |
const $ = getjQuery() | |
const typeEvent = getTypeEvent(event) | |
const inNamespace = event !== typeEvent | |
const isNative = nativeEvents.has(typeEvent) | |
let jQueryEvent | |
let bubbles = true | |
let nativeDispatch = true | |
let defaultPrevented = false | |
let evt = null | |
if (inNamespace && $) { | |
jQueryEvent = $.Event(event, args) | |
$(element).trigger(jQueryEvent) | |
bubbles = !jQueryEvent.isPropagationStopped() | |
nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() | |
defaultPrevented = jQueryEvent.isDefaultPrevented() | |
} | |
if (isNative) { | |
evt = document.createEvent('HTMLEvents') | |
evt.initEvent(typeEvent, bubbles, true) | |
} else { | |
evt = new CustomEvent(event, { | |
bubbles, | |
cancelable: true | |
}) | |
} | |
// merge custom information in our event | |
if (typeof args !== 'undefined') { | |
Object.keys(args).forEach(key => { | |
Object.defineProperty(evt, key, { | |
get() { | |
return args[key] | |
} | |
}) | |
}) | |
} | |
if (defaultPrevented) { | |
evt.preventDefault() | |
} | |
if (nativeDispatch) { | |
element.dispatchEvent(evt) | |
} | |
if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { | |
jQueryEvent.preventDefault() | |
} | |
return evt | |
} | |
} | |
export default EventHandler |