event-handler.js 8.77 KiB
/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.3.1): dom/event-handler.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */
import { jQuery as $ } from '../util/index'
import { createCustomEvent, defaultPreventedPreservedOnDispatch } from './polyfill'
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
const namespaceRegex = /[^.]*(?=\..*)\.|.*/
const stripNameRegex = /\..*/
const keyEventRegex = /^key/
const stripUidRegex = /::\d+$/
const eventRegistry = {} // Events storage
let uidEvent = 1
const customEvents = {
  mouseenter: 'mouseover',
  mouseleave: 'mouseout'
const nativeEvents = [
  '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',
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
'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 fixEvent(event, element) { // Add which for key events if (event.which === null && keyEventRegex.test(event.type)) { event.which = event.charCode === null ? event.keyCode : event.charCode } event.delegateTarget = element } function bootstrapHandler(element, fn) { return function handler(event) { fixEvent(event, 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) { fixEvent(event, target) if (handler.oneOff) { EventHandler.off(element, event.type, fn) } return fn.apply(target, [event]) } } } // To please ESLint return null } } function findHandler(events, handler, delegationSelector = null) { for (const uid of Object.keys(events)) { const event = events[uid]
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { return events[uid] } } return null } function normalizeParams(originalTypeEvent, handler, delegationFn) { const delegation = typeof handler === 'string' const originalHandler = delegation ? delegationFn : handler // allow to get the native events from namespaced events ('click.bs.button' --> 'click') let typeEvent = originalTypeEvent.replace(stripNameRegex, '') const custom = customEvents[typeEvent] if (custom) { typeEvent = custom } const isNative = nativeEvents.indexOf(typeEvent) > -1 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 } 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 === null) { return }
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
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.indexOf(namespace) > -1) { const event = storeElementEvent[handlerKey] removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) } }) } 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.charAt(0) === '.' 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.substr(1)) }) } const storeElementEvent = events[typeEvent] || {} Object.keys(storeElementEvent) .forEach(keyHandlers => { const handlerKey = keyHandlers.replace(stripUidRegex, '') if (!inNamespace || originalTypeEvent.indexOf(handlerKey) > -1) { const event = storeElementEvent[keyHandlers] removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) } }) }, trigger(element, event, args) { if (typeof event !== 'string' || !element) { return null }
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
const typeEvent = event.replace(stripNameRegex, '') const inNamespace = event !== typeEvent const isNative = nativeEvents.indexOf(typeEvent) > -1 let jQueryEvent let bubbles = true let nativeDispatch = true let defaultPrevented = false let evt = null if (inNamespace && typeof $ !== 'undefined') { 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 = createCustomEvent(event, { bubbles, cancelable: true }) } // merge custom informations 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 (!defaultPreventedPreservedOnDispatch) { Object.defineProperty(evt, 'defaultPrevented', { get: () => true }) } } if (nativeDispatch) { element.dispatchEvent(evt) } if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { jQueryEvent.preventDefault() } return evt } } export default EventHandler