You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

353 line
11 KiB

  1. /**
  2. * --------------------------------------------------------------------------
  3. * Bootstrap (v5.1.3): dom/event-handler.js
  4. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  5. * --------------------------------------------------------------------------
  6. */
  7. define([
  8. "../util/index"
  9. ], function(Util) {
  10. 'use strict';
  11. const getjQuery = Util.getjQuery;
  12. /**
  13. * ------------------------------------------------------------------------
  14. * Constants
  15. * ------------------------------------------------------------------------
  16. */
  17. const namespaceRegex = /[^.]*(?=\..*)\.|.*/
  18. const stripNameRegex = /\..*/
  19. const stripUidRegex = /::\d+$/
  20. const eventRegistry = {} // Events storage
  21. let uidEvent = 1
  22. const customEvents = {
  23. mouseenter: 'mouseover',
  24. mouseleave: 'mouseout'
  25. }
  26. const customEventsRegex = /^(mouseenter|mouseleave)/i
  27. const nativeEvents = new Set([
  28. 'click',
  29. 'dblclick',
  30. 'mouseup',
  31. 'mousedown',
  32. 'contextmenu',
  33. 'mousewheel',
  34. 'DOMMouseScroll',
  35. 'mouseover',
  36. 'mouseout',
  37. 'mousemove',
  38. 'selectstart',
  39. 'selectend',
  40. 'keydown',
  41. 'keypress',
  42. 'keyup',
  43. 'orientationchange',
  44. 'touchstart',
  45. 'touchmove',
  46. 'touchend',
  47. 'touchcancel',
  48. 'pointerdown',
  49. 'pointermove',
  50. 'pointerup',
  51. 'pointerleave',
  52. 'pointercancel',
  53. 'gesturestart',
  54. 'gesturechange',
  55. 'gestureend',
  56. 'focus',
  57. 'blur',
  58. 'change',
  59. 'reset',
  60. 'select',
  61. 'submit',
  62. 'focusin',
  63. 'focusout',
  64. 'load',
  65. 'unload',
  66. 'beforeunload',
  67. 'resize',
  68. 'move',
  69. 'DOMContentLoaded',
  70. 'readystatechange',
  71. 'error',
  72. 'abort',
  73. 'scroll'
  74. ])
  75. /**
  76. * ------------------------------------------------------------------------
  77. * Private methods
  78. * ------------------------------------------------------------------------
  79. */
  80. function getUidEvent(element, uid) {
  81. return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++
  82. }
  83. function getEvent(element) {
  84. const uid = getUidEvent(element)
  85. element.uidEvent = uid
  86. eventRegistry[uid] = eventRegistry[uid] || {}
  87. return eventRegistry[uid]
  88. }
  89. function bootstrapHandler(element, fn) {
  90. return function handler(event) {
  91. event.delegateTarget = element
  92. if (handler.oneOff) {
  93. EventHandler.off(element, event.type, fn)
  94. }
  95. return fn.apply(element, [event])
  96. }
  97. }
  98. function bootstrapDelegationHandler(element, selector, fn) {
  99. return function handler(event) {
  100. const domElements = element.querySelectorAll(selector)
  101. for (let {target} = event; target && target !== this; target = target.parentNode) {
  102. for (let i = domElements.length; i--;) {
  103. if (domElements[i] === target) {
  104. event.delegateTarget = target
  105. if (handler.oneOff) {
  106. EventHandler.off(element, event.type, selector, fn)
  107. }
  108. return fn.apply(target, [event])
  109. }
  110. }
  111. }
  112. // To please ESLint
  113. return null
  114. }
  115. }
  116. function findHandler(events, handler, delegationSelector = null) {
  117. const uidEventList = Object.keys(events)
  118. for (let i = 0, len = uidEventList.length; i < len; i++) {
  119. const event = events[uidEventList[i]]
  120. if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {
  121. return event
  122. }
  123. }
  124. return null
  125. }
  126. function normalizeParams(originalTypeEvent, handler, delegationFn) {
  127. const delegation = typeof handler === 'string'
  128. const originalHandler = delegation ? delegationFn : handler
  129. let typeEvent = getTypeEvent(originalTypeEvent)
  130. const isNative = nativeEvents.has(typeEvent)
  131. if (!isNative) {
  132. typeEvent = originalTypeEvent
  133. }
  134. return [delegation, originalHandler, typeEvent]
  135. }
  136. function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
  137. if (typeof originalTypeEvent !== 'string' || !element) {
  138. return
  139. }
  140. if (!handler) {
  141. handler = delegationFn
  142. delegationFn = null
  143. }
  144. // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position
  145. // this prevents the handler from being dispatched the same way as mouseover or mouseout does
  146. if (customEventsRegex.test(originalTypeEvent)) {
  147. const wrapFn = fn => {
  148. return function (event) {
  149. if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {
  150. return fn.call(this, event)
  151. }
  152. }
  153. }
  154. if (delegationFn) {
  155. delegationFn = wrapFn(delegationFn)
  156. } else {
  157. handler = wrapFn(handler)
  158. }
  159. }
  160. const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
  161. const events = getEvent(element)
  162. const handlers = events[typeEvent] || (events[typeEvent] = {})
  163. const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)
  164. if (previousFn) {
  165. previousFn.oneOff = previousFn.oneOff && oneOff
  166. return
  167. }
  168. const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
  169. const fn = delegation ?
  170. bootstrapDelegationHandler(element, handler, delegationFn) :
  171. bootstrapHandler(element, handler)
  172. fn.delegationSelector = delegation ? handler : null
  173. fn.originalHandler = originalHandler
  174. fn.oneOff = oneOff
  175. fn.uidEvent = uid
  176. handlers[uid] = fn
  177. element.addEventListener(typeEvent, fn, delegation)
  178. }
  179. function removeHandler(element, events, typeEvent, handler, delegationSelector) {
  180. const fn = findHandler(events[typeEvent], handler, delegationSelector)
  181. if (!fn) {
  182. return
  183. }
  184. element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))
  185. delete events[typeEvent][fn.uidEvent]
  186. }
  187. function removeNamespacedHandlers(element, events, typeEvent, namespace) {
  188. const storeElementEvent = events[typeEvent] || {}
  189. Object.keys(storeElementEvent).forEach(handlerKey => {
  190. if (handlerKey.includes(namespace)) {
  191. const event = storeElementEvent[handlerKey]
  192. removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
  193. }
  194. })
  195. }
  196. function getTypeEvent(event) {
  197. // allow to get the native events from namespaced events ('click.bs.button' --> 'click')
  198. event = event.replace(stripNameRegex, '')
  199. return customEvents[event] || event
  200. }
  201. return {
  202. on: function(element, event, handler, delegationFn) {
  203. addHandler(element, event, handler, delegationFn, false)
  204. },
  205. one: function(element, event, handler, delegationFn) {
  206. addHandler(element, event, handler, delegationFn, true)
  207. },
  208. off: function(element, originalTypeEvent, handler, delegationFn) {
  209. if (typeof originalTypeEvent !== 'string' || !element) {
  210. return
  211. }
  212. const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
  213. const inNamespace = typeEvent !== originalTypeEvent
  214. const events = getEvent(element)
  215. const isNamespace = originalTypeEvent.startsWith('.')
  216. if (typeof originalHandler !== 'undefined') {
  217. // Simplest case: handler is passed, remove that listener ONLY.
  218. if (!events || !events[typeEvent]) {
  219. return
  220. }
  221. removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)
  222. return
  223. }
  224. if (isNamespace) {
  225. Object.keys(events).forEach(elementEvent => {
  226. removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))
  227. })
  228. }
  229. const storeElementEvent = events[typeEvent] || {}
  230. Object.keys(storeElementEvent).forEach(keyHandlers => {
  231. const handlerKey = keyHandlers.replace(stripUidRegex, '')
  232. if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
  233. const event = storeElementEvent[keyHandlers]
  234. removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
  235. }
  236. })
  237. },
  238. trigger: function(element, event, args) {
  239. if (typeof event !== 'string' || !element) {
  240. return null
  241. }
  242. const $ = getjQuery()
  243. const typeEvent = getTypeEvent(event)
  244. const inNamespace = event !== typeEvent
  245. const isNative = nativeEvents.has(typeEvent)
  246. let jQueryEvent
  247. let bubbles = true
  248. let nativeDispatch = true
  249. let defaultPrevented = false
  250. let evt = null
  251. if (inNamespace && $) {
  252. jQueryEvent = $.Event(event, args)
  253. $(element).trigger(jQueryEvent)
  254. bubbles = !jQueryEvent.isPropagationStopped()
  255. nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()
  256. defaultPrevented = jQueryEvent.isDefaultPrevented()
  257. }
  258. if (isNative) {
  259. evt = document.createEvent('HTMLEvents')
  260. evt.initEvent(typeEvent, bubbles, true)
  261. } else {
  262. evt = new CustomEvent(event, {
  263. bubbles,
  264. cancelable: true
  265. })
  266. }
  267. // merge custom information in our event
  268. if (typeof args !== 'undefined') {
  269. Object.keys(args).forEach(key => {
  270. Object.defineProperty(evt, key, {
  271. get() {
  272. return args[key]
  273. }
  274. })
  275. })
  276. }
  277. if (defaultPrevented) {
  278. evt.preventDefault()
  279. }
  280. if (nativeDispatch) {
  281. element.dispatchEvent(evt)
  282. }
  283. if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {
  284. jQueryEvent.preventDefault()
  285. }
  286. return evt
  287. }
  288. }
  289. });