Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

twbs / bootstrap / 826720599

10 May 2021 - 4:06 coverage decreased (-0.1%) to 95.456%
826720599

Pull #33904

github

GitHub
Merge 46e3fa0f1 into 488fd8afc
Pull Request #33904: Add Fathom Analytics to v5 docs

1149 of 1246 branches covered (92.22%)

Branch coverage included in aggregate %.

2065 of 2121 relevant lines covered (97.36%)

120.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.27
/js/src/dom/event-handler.js
1
/**
2
 * --------------------------------------------------------------------------
3
 * Bootstrap (v5.0.0): dom/event-handler.js
4
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
 * --------------------------------------------------------------------------
6
 */
7

8
import { getjQuery } from '../util/index'
9

10
/**
11
 * ------------------------------------------------------------------------
12
 * Constants
13
 * ------------------------------------------------------------------------
14
 */
15

16
const namespaceRegex = /[^.]*(?=\..*)\.|.*/
14×
17
const stripNameRegex = /\..*/
14×
18
const stripUidRegex = /::\d+$/
14×
19
const eventRegistry = {} // Events storage
14×
20
let uidEvent = 1
14×
21
const customEvents = {
14×
22
  mouseenter: 'mouseover',
23
  mouseleave: 'mouseout'
24
}
25
const customEventsRegex = /^(mouseenter|mouseleave)/i
14×
26
const nativeEvents = new Set([
14×
27
  'click',
28
  'dblclick',
29
  'mouseup',
30
  'mousedown',
31
  'contextmenu',
32
  'mousewheel',
33
  'DOMMouseScroll',
34
  'mouseover',
35
  'mouseout',
36
  'mousemove',
37
  'selectstart',
38
  'selectend',
39
  'keydown',
40
  'keypress',
41
  'keyup',
42
  'orientationchange',
43
  'touchstart',
44
  'touchmove',
45
  'touchend',
46
  'touchcancel',
47
  'pointerdown',
48
  'pointermove',
49
  'pointerup',
50
  'pointerleave',
51
  'pointercancel',
52
  'gesturestart',
53
  'gesturechange',
54
  'gestureend',
55
  'focus',
56
  'blur',
57
  'change',
58
  'reset',
59
  'select',
60
  'submit',
61
  'focusin',
62
  'focusout',
63
  'load',
64
  'unload',
65
  'beforeunload',
66
  'resize',
67
  'move',
68
  'DOMContentLoaded',
69
  'readystatechange',
70
  'error',
71
  'abort',
72
  'scroll'
73
])
74

75
/**
76
 * ------------------------------------------------------------------------
77
 * Private methods
78
 * ------------------------------------------------------------------------
79
 */
80

81
function getUidEvent(element, uid) {
82
  return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++
6,385×
83
}
84

85
function getEvent(element) {
86
  const uid = getUidEvent(element)
4,472×
87

88
  element.uidEvent = uid
4,472×
89
  eventRegistry[uid] = eventRegistry[uid] || {}
4,472×
90

91
  return eventRegistry[uid]
4,472×
92
}
93

94
function bootstrapHandler(element, fn) {
95
  return function handler(event) {
1,806×
96
    event.delegateTarget = element
624×
97

98
    if (handler.oneOff) {
624×
99
      EventHandler.off(element, event.type, fn)
208×
100
    }
101

102
    return fn.apply(element, [event])
624×
103
  }
104
}
105

106
function bootstrapDelegationHandler(element, selector, fn) {
107
  return function handler(event) {
107×
108
    const domElements = element.querySelectorAll(selector)
1,029×
109

110
    for (let { target } = event; target && target !== this; target = target.parentNode) {
1,029×
111
      for (let i = domElements.length; i--;) {
4,456×
112
        if (domElements[i] === target) {
360×
113
          event.delegateTarget = target
118×
114

115
          if (handler.oneOff) {
118×
116
            // eslint-disable-next-line unicorn/consistent-destructuring
117
            EventHandler.off(element, event.type, selector, fn)
1×
118
          }
119

120
          return fn.apply(target, [event])
118×
121
        }
122
      }
123
    }
124

125
    // To please ESLint
126
    return null
911×
127
  }
128
}
129

130
function findHandler(events, handler, delegationSelector = null) {
Branches [[6, 0]] missed.
131
  const uidEventList = Object.keys(events)
4,491×
132

133
  for (let i = 0, len = uidEventList.length; i < len; i++) {
4,491×
134
    const event = events[uidEventList[i]]
2,710×
135

136
    if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {
2,710×
137
      return event
2,578×
138
    }
139
  }
140

141
  return null
1,913×
142
}
143

144
function normalizeParams(originalTypeEvent, handler, delegationFn) {
145
  const delegation = typeof handler === 'string'
4,472×
146
  const originalHandler = delegation ? delegationFn : handler
4,472×
147

148
  let typeEvent = getTypeEvent(originalTypeEvent)
4,472×
149
  const isNative = nativeEvents.has(typeEvent)
4,472×
150

151
  if (!isNative) {
4,472×
152
    typeEvent = originalTypeEvent
463×
153
  }
154

155
  return [delegation, originalHandler, typeEvent]
4,472×
156
}
157

158
function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
159
  if (typeof originalTypeEvent !== 'string' || !element) {
3,538×
160
    return
87×
161
  }
162

163
  if (!handler) {
3,451×
164
    handler = delegationFn
293×
165
    delegationFn = null
293×
166
  }
167

168
  // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position
169
  // this prevents the handler from being dispatched the same way as mouseover or mouseout does
170
  if (customEventsRegex.test(originalTypeEvent)) {
3,451×
171
    const wrapFn = fn => {
251×
172
      return function (event) {
251×
173
        if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {
39×
174
          return fn.call(this, event)
22×
175
        }
176
      }
177
    }
178

179
    if (delegationFn) {
251×
180
      delegationFn = wrapFn(delegationFn)
2×
181
    } else {
182
      handler = wrapFn(handler)
249×
183
    }
184
  }
185

186
  const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
3,451×
187
  const events = getEvent(element)
3,451×
188
  const handlers = events[typeEvent] || (events[typeEvent] = {})
3,451×
189
  const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)
3,451×
190

191
  if (previousFn) {
3,451×
192
    previousFn.oneOff = previousFn.oneOff && oneOff
Branches [[21, 1]] missed. 1,538×
193

194
    return
1,538×
195
  }
196

197
  const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
1,913×
198
  const fn = delegation ?
1,913×
199
    bootstrapDelegationHandler(element, handler, delegationFn) :
200
    bootstrapHandler(element, handler)
201

202
  fn.delegationSelector = delegation ? handler : null
1,913×
203
  fn.originalHandler = originalHandler
1,913×
204
  fn.oneOff = oneOff
1,913×
205
  fn.uidEvent = uid
1,913×
206
  handlers[uid] = fn
1,913×
207

208
  element.addEventListener(typeEvent, fn, delegation)
1,913×
209
}
210

211
function removeHandler(element, events, typeEvent, handler, delegationSelector) {
212
  const fn = findHandler(events[typeEvent], handler, delegationSelector)
1,040×
213

214
  if (!fn) {
Branches [[24, 0]] missed. 1,040×
UNCOV
215
    return
!
216
  }
217

218
  element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))
1,040×
219
  delete events[typeEvent][fn.uidEvent]
1,040×
220
}
221

222
function removeNamespacedHandlers(element, events, typeEvent, namespace) {
223
  const storeElementEvent = events[typeEvent] || {}
Branches [[25, 1]] missed. 32×
224

225
  Object.keys(storeElementEvent).forEach(handlerKey => {
32×
226
    if (handlerKey.includes(namespace)) {
29×
227
      const event = storeElementEvent[handlerKey]
26×
228

229
      removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
26×
230
    }
231
  })
232
}
233

234
function getTypeEvent(event) {
235
  // allow to get the native events from namespaced events ('click.bs.button' --> 'click')
236
  event = event.replace(stripNameRegex, '')
5,193×
237
  return customEvents[event] || event
5,193×
238
}
239

240
const EventHandler = {
14×
241
  on(element, event, handler, delegationFn) {
242
    addHandler(element, event, handler, delegationFn, false)
3,323×
243
  },
244

245
  one(element, event, handler, delegationFn) {
246
    addHandler(element, event, handler, delegationFn, true)
215×
247
  },
248

249
  off(element, originalTypeEvent, handler, delegationFn) {
250
    if (typeof originalTypeEvent !== 'string' || !element) {
1,027×
251
      return
6×
252
    }
253

254
    const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
1,021×
255
    const inNamespace = typeEvent !== originalTypeEvent
1,021×
256
    const events = getEvent(element)
1,021×
257
    const isNamespace = originalTypeEvent.startsWith('.')
1,021×
258

259
    if (typeof originalHandler !== 'undefined') {
1,021×
260
      // Simplest case: handler is passed, remove that listener ONLY.
261
      if (!events || !events[typeEvent]) {
Branches [[31, 0]] missed. 855×
262
        return
!
263
      }
264

265
      removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)
855×
266
      return
855×
267
    }
268

269
    if (isNamespace) {
166×
270
      Object.keys(events).forEach(elementEvent => {
17×
271
        removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))
32×
272
      })
273
    }
274

275
    const storeElementEvent = events[typeEvent] || {}
166×
276
    Object.keys(storeElementEvent).forEach(keyHandlers => {
166×
277
      const handlerKey = keyHandlers.replace(stripUidRegex, '')
160×
278

279
      if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
160×
280
        const event = storeElementEvent[keyHandlers]
159×
281

282
        removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
159×
283
      }
284
    })
285
  },
286

287
  trigger(element, event, args) {
288
    if (typeof event !== 'string' || !element) {
734×
289
      return null
13×
290
    }
291

292
    const $ = getjQuery()
721×
293
    const typeEvent = getTypeEvent(event)
721×
294
    const inNamespace = event !== typeEvent
721×
295
    const isNative = nativeEvents.has(typeEvent)
721×
296

297
    let jQueryEvent
298
    let bubbles = true
721×
299
    let nativeDispatch = true
721×
300
    let defaultPrevented = false
721×
301
    let evt = null
721×
302

303
    if (inNamespace && $) {
Branches [[40, 0]] missed. 721×
304
      jQueryEvent = $.Event(event, args)
!
305

306
      $(element).trigger(jQueryEvent)
!
307
      bubbles = !jQueryEvent.isPropagationStopped()
!
308
      nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()
!
309
      defaultPrevented = jQueryEvent.isDefaultPrevented()
!
310
    }
311

312
    if (isNative) {
721×
313
      evt = document.createEvent('HTMLEvents')
12×
314
      evt.initEvent(typeEvent, bubbles, true)
12×
315
    } else {
316
      evt = new CustomEvent(event, {
709×
317
        bubbles,
318
        cancelable: true
319
      })
320
    }
321

322
    // merge custom information in our event
323
    if (typeof args !== 'undefined') {
721×
324
      Object.keys(args).forEach(key => {
362×
325
        Object.defineProperty(evt, key, {
509×
326
          get() {
327
            return args[key]
21×
328
          }
329
        })
330
      })
331
    }
332

333
    if (defaultPrevented) {
Branches [[44, 0]] missed. 721×
334
      evt.preventDefault()
!
335
    }
336

337
    if (nativeDispatch) {
Branches [[45, 1]] missed. 721×
338
      element.dispatchEvent(evt)
721×
339
    }
340

341
    if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {
Branches [[46, 0]] missed. 721×
342
      jQueryEvent.preventDefault()
!
343
    }
344

345
    return evt
721×
346
  }
347
}
348

349
export default EventHandler
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2022 Coveralls, Inc