0
0
mirror of https://github.com/twbs/bootstrap.git synced 2024-12-12 00:08:59 +01:00
Bootstrap/js/dist/dom/event-handler.js

238 lines
9.3 KiB
JavaScript
Raw Normal View History

2019-03-01 17:31:34 +01:00
/*!
* Bootstrap event-handler.js v5.3.0-alpha1 (https://getbootstrap.com/)
2022-05-13 08:07:23 +02:00
* Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
2020-06-16 20:50:01 +02:00
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
2019-03-01 17:31:34 +01:00
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) :
2022-05-13 08:07:23 +02:00
typeof define === 'function' && define.amd ? define(['../util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EventHandler = factory(global.Index));
})(this, (function (index_js) { 'use strict';
2019-03-01 17:31:34 +01:00
2021-08-04 17:41:51 +02:00
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.3.0-alpha1): dom/event-handler.js
2021-08-04 17:41:51 +02:00
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
2019-03-01 17:31:34 +01:00
/**
* Constants
*/
2021-03-23 17:26:54 +01:00
const namespaceRegex = /[^.]*(?=\..*)\.|.*/;
const stripNameRegex = /\..*/;
const stripUidRegex = /::\d+$/;
const eventRegistry = {}; // Events storage
let uidEvent = 1;
const customEvents = {
2019-03-01 17:31:34 +01:00
mouseenter: 'mouseover',
mouseleave: 'mouseout'
};
2021-03-23 17:26:54 +01:00
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']);
2019-03-01 17:31:34 +01:00
/**
* Private methods
*/
function makeEventUid(element, uid) {
2021-03-23 17:26:54 +01:00
return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;
2019-03-01 17:31:34 +01:00
}
function getElementEvents(element) {
const uid = makeEventUid(element);
2019-03-01 17:31:34 +01:00
element.uidEvent = uid;
2019-03-11 16:13:30 +01:00
eventRegistry[uid] = eventRegistry[uid] || {};
return eventRegistry[uid];
2019-03-01 17:31:34 +01:00
}
function bootstrapHandler(element, fn) {
return function handler(event) {
hydrateObj(event, {
delegateTarget: element
});
2019-03-01 17:31:34 +01:00
if (handler.oneOff) {
EventHandler.off(element, event.type, fn);
}
return fn.apply(element, [event]);
};
}
function bootstrapDelegationHandler(element, selector, fn) {
return function handler(event) {
2021-03-23 17:26:54 +01:00
const domElements = element.querySelectorAll(selector);
for (let {
target
} = event; target && target !== this; target = target.parentNode) {
2022-05-13 08:07:23 +02:00
for (const domElement of domElements) {
if (domElement !== target) {
continue;
}
hydrateObj(event, {
delegateTarget: target
});
2022-05-13 08:07:23 +02:00
if (handler.oneOff) {
EventHandler.off(element, event.type, selector, fn);
2019-03-01 17:31:34 +01:00
}
2022-05-13 08:07:23 +02:00
return fn.apply(target, [event]);
}
}
2019-03-01 17:31:34 +01:00
};
}
function findHandler(events, callable, delegationSelector = null) {
return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);
2019-03-01 17:31:34 +01:00
}
2022-05-13 08:07:23 +02:00
function normalizeParameters(originalTypeEvent, handler, delegationFunction) {
const isDelegated = typeof handler === 'string';
// todo: tooltip passes `false` instead of selector, so we need to check
const callable = isDelegated ? delegationFunction : handler || delegationFunction;
let typeEvent = getTypeEvent(originalTypeEvent);
2022-05-13 08:07:23 +02:00
if (!nativeEvents.has(typeEvent)) {
2019-03-01 17:31:34 +01:00
typeEvent = originalTypeEvent;
}
return [isDelegated, callable, typeEvent];
2019-03-01 17:31:34 +01:00
}
2022-05-13 08:07:23 +02:00
function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {
2019-03-01 17:31:34 +01:00
if (typeof originalTypeEvent !== 'string' || !element) {
return;
}
let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);
2019-03-01 17:31:34 +01:00
// 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
2022-05-13 08:07:23 +02:00
if (originalTypeEvent in customEvents) {
const wrapFunction = fn => {
return function (event) {
if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {
return fn.call(this, event);
}
};
};
callable = wrapFunction(callable);
2019-03-01 17:31:34 +01:00
}
const events = getElementEvents(element);
2021-03-23 17:26:54 +01:00
const handlers = events[typeEvent] || (events[typeEvent] = {});
const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);
2022-05-13 08:07:23 +02:00
if (previousFunction) {
previousFunction.oneOff = previousFunction.oneOff && oneOff;
2019-03-01 17:31:34 +01:00
return;
}
const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));
const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);
fn.delegationSelector = isDelegated ? handler : null;
fn.callable = callable;
2019-03-01 17:31:34 +01:00
fn.oneOff = oneOff;
fn.uidEvent = uid;
handlers[uid] = fn;
element.addEventListener(typeEvent, fn, isDelegated);
2019-03-01 17:31:34 +01:00
}
function removeHandler(element, events, typeEvent, handler, delegationSelector) {
2021-03-23 17:26:54 +01:00
const fn = findHandler(events[typeEvent], handler, delegationSelector);
2019-07-12 23:56:26 +02:00
if (!fn) {
2019-03-01 17:31:34 +01:00
return;
}
element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));
delete events[typeEvent][fn.uidEvent];
}
function removeNamespacedHandlers(element, events, typeEvent, namespace) {
2021-03-23 17:26:54 +01:00
const storeElementEvent = events[typeEvent] || {};
for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
2020-11-23 14:17:16 +01:00
if (handlerKey.includes(namespace)) {
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);
2019-03-01 17:31:34 +01:00
}
2022-05-13 08:07:23 +02:00
}
2019-03-01 17:31:34 +01:00
}
function getTypeEvent(event) {
// allow to get the native events from namespaced events ('click.bs.button' --> 'click')
event = event.replace(stripNameRegex, '');
return customEvents[event] || event;
}
2021-03-23 17:26:54 +01:00
const EventHandler = {
2022-05-13 08:07:23 +02:00
on(element, event, handler, delegationFunction) {
addHandler(element, event, handler, delegationFunction, false);
2019-03-01 17:31:34 +01:00
},
2022-05-13 08:07:23 +02:00
one(element, event, handler, delegationFunction) {
addHandler(element, event, handler, delegationFunction, true);
2019-03-01 17:31:34 +01:00
},
2022-05-13 08:07:23 +02:00
off(element, originalTypeEvent, handler, delegationFunction) {
2019-03-01 17:31:34 +01:00
if (typeof originalTypeEvent !== 'string' || !element) {
return;
}
const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);
2021-03-23 17:26:54 +01:00
const inNamespace = typeEvent !== originalTypeEvent;
const events = getElementEvents(element);
const storeElementEvent = events[typeEvent] || {};
2021-03-23 17:26:54 +01:00
const isNamespace = originalTypeEvent.startsWith('.');
if (typeof callable !== 'undefined') {
2019-03-01 17:31:34 +01:00
// Simplest case: handler is passed, remove that listener ONLY.
if (!Object.keys(storeElementEvent).length) {
2019-03-01 17:31:34 +01:00
return;
}
removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);
2019-03-01 17:31:34 +01:00
return;
}
if (isNamespace) {
2022-05-13 08:07:23 +02:00
for (const elementEvent of Object.keys(events)) {
2019-10-08 08:39:10 +02:00
removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));
2022-05-13 08:07:23 +02:00
}
2019-03-01 17:31:34 +01:00
}
for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {
2021-03-23 17:26:54 +01:00
const handlerKey = keyHandlers.replace(stripUidRegex, '');
2020-11-23 14:17:16 +01:00
if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);
2019-03-01 17:31:34 +01:00
}
2022-05-13 08:07:23 +02:00
}
2019-03-01 17:31:34 +01:00
},
2021-03-23 17:26:54 +01:00
trigger(element, event, args) {
2019-03-01 17:31:34 +01:00
if (typeof event !== 'string' || !element) {
return null;
}
const $ = index_js.getjQuery();
const typeEvent = getTypeEvent(event);
2021-03-23 17:26:54 +01:00
const inNamespace = event !== typeEvent;
2022-05-13 08:07:23 +02:00
let jQueryEvent = null;
2021-03-23 17:26:54 +01:00
let bubbles = true;
let nativeDispatch = true;
let defaultPrevented = false;
2019-08-27 15:03:21 +02:00
if (inNamespace && $) {
jQueryEvent = $.Event(event, args);
$(element).trigger(jQueryEvent);
2019-03-01 17:31:34 +01:00
bubbles = !jQueryEvent.isPropagationStopped();
nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();
defaultPrevented = jQueryEvent.isDefaultPrevented();
}
let evt = new Event(event, {
2022-05-13 08:07:23 +02:00
bubbles,
cancelable: true
});
evt = hydrateObj(evt, args);
2019-03-01 17:31:34 +01:00
if (defaultPrevented) {
evt.preventDefault();
}
if (nativeDispatch) {
element.dispatchEvent(evt);
}
2022-05-13 08:07:23 +02:00
if (evt.defaultPrevented && jQueryEvent) {
2019-03-01 17:31:34 +01:00
jQueryEvent.preventDefault();
}
return evt;
}
};
function hydrateObj(obj, meta = {}) {
for (const [key, value] of Object.entries(meta)) {
try {
obj[key] = value;
} catch (_unused) {
Object.defineProperty(obj, key, {
configurable: true,
get() {
return value;
}
});
}
}
return obj;
}
2019-03-01 17:31:34 +01:00
return EventHandler;
2021-10-05 17:50:18 +02:00
}));
//# sourceMappingURL=event-handler.js.map