| /** |
| * -------------------------------------------------------------------------- |
| * 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 (const domElement of domElements) { |
| if (domElement !== target) { |
| continue |
| } |
| |
| event.delegateTarget = target |
| |
| if (handler.oneOff) { |
| EventHandler.off(element, event.type, selector, fn) |
| } |
| |
| return fn.apply(target, [event]) |
| } |
| } |
| } |
| } |
| |
| function findHandler(events, handler, delegationSelector = null) { |
| return Object.values(events) |
| .find(event => event.originalHandler === handler && event.delegationSelector === delegationSelector) |
| } |
| |
| function normalizeParameters(originalTypeEvent, handler, delegationFunction) { |
| const delegation = typeof handler === 'string' |
| const originalHandler = delegation ? delegationFunction : handler |
| let typeEvent = getTypeEvent(originalTypeEvent) |
| |
| if (!nativeEvents.has(typeEvent)) { |
| typeEvent = originalTypeEvent |
| } |
| |
| return [delegation, originalHandler, typeEvent] |
| } |
| |
| function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { |
| if (typeof originalTypeEvent !== 'string' || !element) { |
| return |
| } |
| |
| if (!handler) { |
| handler = delegationFunction |
| delegationFunction = 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 wrapFunction = fn => { |
| return function (event) { |
| if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) { |
| return fn.call(this, event) |
| } |
| } |
| } |
| |
| if (delegationFunction) { |
| delegationFunction = wrapFunction(delegationFunction) |
| } else { |
| handler = wrapFunction(handler) |
| } |
| } |
| |
| const [delegation, originalHandler, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction) |
| const events = getEvent(element) |
| const handlers = events[typeEvent] || (events[typeEvent] = {}) |
| const previousFunction = findHandler(handlers, originalHandler, delegation ? handler : null) |
| |
| if (previousFunction) { |
| previousFunction.oneOff = previousFunction.oneOff && oneOff |
| |
| return |
| } |
| |
| const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) |
| const fn = delegation ? |
| bootstrapDelegationHandler(element, handler, delegationFunction) : |
| 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] || {} |
| |
| for (const handlerKey of Object.keys(storeElementEvent)) { |
| 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, delegationFunction) { |
| addHandler(element, event, handler, delegationFunction, false) |
| }, |
| |
| one(element, event, handler, delegationFunction) { |
| addHandler(element, event, handler, delegationFunction, true) |
| }, |
| |
| off(element, originalTypeEvent, handler, delegationFunction) { |
| if (typeof originalTypeEvent !== 'string' || !element) { |
| return |
| } |
| |
| const [delegation, originalHandler, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction) |
| 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) { |
| for (const elementEvent of Object.keys(events)) { |
| removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)) |
| } |
| } |
| |
| const storeElementEvent = events[typeEvent] || {} |
| for (const keyHandlers of Object.keys(storeElementEvent)) { |
| 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 |
| |
| let jQueryEvent = null |
| let bubbles = true |
| let nativeDispatch = true |
| let defaultPrevented = false |
| |
| if (inNamespace && $) { |
| jQueryEvent = $.Event(event, args) |
| |
| $(element).trigger(jQueryEvent) |
| bubbles = !jQueryEvent.isPropagationStopped() |
| nativeDispatch = !jQueryEvent.isImmediatePropagationStopped() |
| defaultPrevented = jQueryEvent.isDefaultPrevented() |
| } |
| |
| const evt = new Event(event, { bubbles, cancelable: true }) |
| |
| // merge custom information in our event |
| if (typeof args !== 'undefined') { |
| for (const key of Object.keys(args)) { |
| Object.defineProperty(evt, key, { |
| get() { |
| return args[key] |
| } |
| }) |
| } |
| } |
| |
| if (defaultPrevented) { |
| evt.preventDefault() |
| } |
| |
| if (nativeDispatch) { |
| element.dispatchEvent(evt) |
| } |
| |
| if (evt.defaultPrevented && jQueryEvent) { |
| jQueryEvent.preventDefault() |
| } |
| |
| return evt |
| } |
| } |
| |
| export default EventHandler |