]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Refactor util plugin and some tests
authorJohann-S <johann.servoire@gmail.com>
Fri, 14 Sep 2018 12:27:30 +0000 (14:27 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Wed, 20 Feb 2019 20:05:45 +0000 (22:05 +0200)
12 files changed:
build/rollup.config.js
js/src/collapse.js
js/src/dom/data.js
js/src/dom/eventHandler.js
js/src/dom/polyfill.js
js/src/dom/selectorEngine.js
js/src/dropdown.js
js/src/modal.js
js/src/tooltip.js
js/src/util.js
js/tests/unit/util.js
site/docs/4.3/assets/js/src/application.js

index b3a7929ec42ebfc116a06fb17bd6229d67309da6..e2819f8c04268fc78fa8645e6564e47f4655ecde 100644 (file)
@@ -8,7 +8,7 @@ const banner  = require('./banner.js')
 const BUNDLE  = process.env.BUNDLE === 'true'
 
 let fileDest  = 'bootstrap.js'
-const external = ['jquery', 'popper.js']
+const external = ['popper.js']
 const plugins = [
   babel({
     exclude: 'node_modules/**', // Only transpile our source code
index dae60e1227f64af9a656c1e6b60ee74c9538a15a..838eea4d1c678f1b1642baf9cc982e8bacbd51e7 100644 (file)
@@ -311,13 +311,13 @@ class Collapse {
     const selector =
       `[data-toggle="collapse"][data-parent="${this._config.parent}"]`
 
-    const elements = Util.makeArray(SelectorEngine.find(selector, parent))
-    elements.forEach((element) => {
-      this._addAriaAndCollapsedClass(
-        Collapse._getTargetFromElement(element),
-        [element]
-      )
-    })
+    Util.makeArray(SelectorEngine.find(selector, parent))
+      .forEach((element) => {
+        this._addAriaAndCollapsedClass(
+          Collapse._getTargetFromElement(element),
+          [element]
+        )
+      })
 
     return parent
   }
index 2dfaad91a416dc023540298a7151a401d6e16e5f..9aa3f13b14b523ce32246e48ae0371e0e383b7b0 100644 (file)
@@ -5,64 +5,62 @@
  * --------------------------------------------------------------------------
  */
 
-const Data = (() => {
-  /**
-   * ------------------------------------------------------------------------
-   * Constants
-   * ------------------------------------------------------------------------
-   */
-
-  const mapData = (() => {
-    const storeData = {}
-    let id = 1
-    return {
-      set(element, key, data) {
-        if (typeof element.key === 'undefined') {
-          element.key = {
-            key,
-            id
-          }
-          id++
-        }
+/**
+ * ------------------------------------------------------------------------
+ * Constants
+ * ------------------------------------------------------------------------
+ */
 
-        storeData[element.key.id] = data
-      },
-      get(element, key) {
-        if (!element || typeof element.key === 'undefined') {
-          return null
+const mapData = (() => {
+  const storeData = {}
+  let id = 1
+  return {
+    set(element, key, data) {
+      if (typeof element.key === 'undefined') {
+        element.key = {
+          key,
+          id
         }
+        id++
+      }
 
-        const keyProperties = element.key
-        if (keyProperties.key === key) {
-          return storeData[keyProperties.id]
-        }
+      storeData[element.key.id] = data
+    },
+    get(element, key) {
+      if (!element || typeof element.key === 'undefined') {
         return null
-      },
-      delete(element, key) {
-        if (typeof element.key === 'undefined') {
-          return
-        }
-
-        const keyProperties = element.key
-        if (keyProperties.key === key) {
-          delete storeData[keyProperties.id]
-          delete element.key
-        }
       }
-    }
-  })()
 
-  return {
-    setData(instance, key, data) {
-      mapData.set(instance, key, data)
-    },
-    getData(instance, key) {
-      return mapData.get(instance, key)
+      const keyProperties = element.key
+      if (keyProperties.key === key) {
+        return storeData[keyProperties.id]
+      }
+      return null
     },
-    removeData(instance, key) {
-      mapData.delete(instance, key)
+    delete(element, key) {
+      if (typeof element.key === 'undefined') {
+        return
+      }
+
+      const keyProperties = element.key
+      if (keyProperties.key === key) {
+        delete storeData[keyProperties.id]
+        delete element.key
+      }
     }
   }
 })()
 
+const Data = {
+  setData(instance, key, data) {
+    mapData.set(instance, key, data)
+  },
+  getData(instance, key) {
+    return mapData.get(instance, key)
+  },
+  removeData(instance, key) {
+    mapData.delete(instance, key)
+  }
+}
+
 export default Data
index 259f575ed933661e8de1dc3e4319d3e040ceb3d4..84570f874cd5c620920131331c2a08d4369803e1 100644 (file)
@@ -8,312 +8,310 @@ import Util from '../util'
  * --------------------------------------------------------------------------
  */
 
-const EventHandler = (() => {
-  /**
-   * ------------------------------------------------------------------------
-   * 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',
-    'error', 'abort', 'scroll'
-  ]
-
-  /**
-   * ------------------------------------------------------------------------
-   * Private methods
-   * ------------------------------------------------------------------------
-   */
-
-  function getUidEvent(element, uid) {
-    return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++
-  }
+/**
+ * ------------------------------------------------------------------------
+ * Constants
+ * ------------------------------------------------------------------------
+ */
 
-  function getEvent(element) {
-    const uid = getUidEvent(element)
-    element.uidEvent = uid
+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',
+  'error', 'abort', 'scroll'
+]
 
-    return eventRegistry[uid] = eventRegistry[uid] || {}
-  }
+/**
+ * ------------------------------------------------------------------------
+ * Private methods
+ * ------------------------------------------------------------------------
+ */
 
-  function fixEvent(event, element) {
-    // Add which for key events
-    if (event.which === null && keyEventRegex.test(event.type)) {
-      event.which = event.charCode !== null ? event.charCode : event.keyCode
-    }
+function getUidEvent(element, uid) {
+  return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++
+}
+
+function getEvent(element) {
+  const uid = getUidEvent(element)
+  element.uidEvent = uid
+
+  return eventRegistry[uid] = eventRegistry[uid] || {}
+}
 
-    event.delegateTarget = element
+function fixEvent(event, element) {
+  // Add which for key events
+  if (event.which === null && keyEventRegex.test(event.type)) {
+    event.which = event.charCode !== null ? event.charCode : event.keyCode
   }
 
-  function bootstrapHandler(element, fn) {
-    return function handler(event) {
-      fixEvent(event, element)
-      if (handler.oneOff) {
-        EventHandler.off(element, event.type, fn)
-      }
+  event.delegateTarget = element
+}
 
-      return fn.apply(element, [event])
+function bootstrapHandler(element, fn) {
+  return function handler(event) {
+    fixEvent(event, element)
+    if (handler.oneOff) {
+      EventHandler.off(element, event.type, fn)
     }
-  }
 
-  function bootstrapDelegationHandler(element, selector, fn) {
-    return function handler(event) {
-      const domElements = element.querySelectorAll(selector)
-      for (let target = event.target; 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(element, [event])
+  }
+}
 
-            return fn.apply(target, [event])
+function bootstrapDelegationHandler(element, selector, fn) {
+  return function handler(event) {
+    const domElements = element.querySelectorAll(selector)
+    for (let target = event.target; 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 in events) {
-      if (!Object.prototype.hasOwnProperty.call(events, uid)) {
-        continue
-      }
+    // To please ESLint
+    return null
+  }
+}
 
-      const event = events[uid]
-      if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {
-        return events[uid]
-      }
+function findHandler(events, handler, delegationSelector = null) {
+  for (const uid in events) {
+    if (!Object.prototype.hasOwnProperty.call(events, uid)) {
+      continue
     }
 
-    return null
+    const event = events[uid]
+    if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {
+      return events[uid]
+    }
   }
 
-  function normalizeParams(originalTypeEvent, handler, delegationFn) {
-    const delegation      = typeof handler === 'string'
-    const originalHandler = delegation ? delegationFn : handler
+  return null
+}
 
-    // allow to get the native events from namespaced events ('click.bs.button' --> 'click')
-    let typeEvent = originalTypeEvent.replace(stripNameRegex, '')
+function normalizeParams(originalTypeEvent, handler, delegationFn) {
+  const delegation      = typeof handler === 'string'
+  const originalHandler = delegation ? delegationFn : handler
 
-    const custom = customEvents[typeEvent]
-    if (custom) {
-      typeEvent = custom
-    }
+  // allow to get the native events from namespaced events ('click.bs.button' --> 'click')
+  let typeEvent = originalTypeEvent.replace(stripNameRegex, '')
 
-    const isNative = nativeEvents.indexOf(typeEvent) > -1
-    if (!isNative) {
-      typeEvent = originalTypeEvent
-    }
+  const custom = customEvents[typeEvent]
+  if (custom) {
+    typeEvent = custom
+  }
 
-    return [delegation, originalHandler, typeEvent]
+  const isNative = nativeEvents.indexOf(typeEvent) > -1
+  if (!isNative) {
+    typeEvent = originalTypeEvent
   }
 
-  function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
-    if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) {
-      return
-    }
+  return [delegation, originalHandler, typeEvent]
+}
 
-    if (!handler) {
-      handler = delegationFn
-      delegationFn = null
-    }
+function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
+  if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) {
+    return
+  }
 
-    const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
+  if (!handler) {
+    handler = delegationFn
+    delegationFn = null
+  }
 
-    const events     = getEvent(element)
-    const handlers   = events[typeEvent] || (events[typeEvent] = {})
-    const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)
+  const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
 
-    if (previousFn) {
-      previousFn.oneOff = previousFn.oneOff && oneOff
-      return
-    }
+  const events     = getEvent(element)
+  const handlers   = events[typeEvent] || (events[typeEvent] = {})
+  const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)
 
-    const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
-    const fn  = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn)
+  if (previousFn) {
+    previousFn.oneOff = previousFn.oneOff && oneOff
+    return
+  }
 
-    fn.delegationSelector = delegation ? handler : null
-    fn.originalHandler = originalHandler
-    fn.oneOff = oneOff
-    fn.uidEvent = uid
-    handlers[uid] = fn
+  const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
+  const fn  = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn)
 
-    element.addEventListener(typeEvent, fn, delegation)
-  }
+  fn.delegationSelector = delegation ? handler : null
+  fn.originalHandler = originalHandler
+  fn.oneOff = oneOff
+  fn.uidEvent = uid
+  handlers[uid] = fn
 
-  function removeHandler(element, events, typeEvent, handler, delegationSelector) {
-    const fn = findHandler(events[typeEvent], handler, delegationSelector)
-    if (fn === null) {
-      return
-    }
+  element.addEventListener(typeEvent, fn, delegation)
+}
 
-    element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))
-    delete events[typeEvent][fn.uidEvent]
+function removeHandler(element, events, typeEvent, handler, delegationSelector) {
+  const fn = findHandler(events[typeEvent], handler, delegationSelector)
+  if (fn === null) {
+    return
   }
 
-  function removeNamespacedHandlers(element, events, typeEvent, namespace) {
-    const storeElementEvent = events[typeEvent] || {}
-    for (const handlerKey in storeElementEvent) {
-      if (!Object.prototype.hasOwnProperty.call(storeElementEvent, handlerKey)) {
-        continue
-      }
+  element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))
+  delete events[typeEvent][fn.uidEvent]
+}
 
-      if (handlerKey.indexOf(namespace) > -1) {
-        const event = storeElementEvent[handlerKey]
-        removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
-      }
+function removeNamespacedHandlers(element, events, typeEvent, namespace) {
+  const storeElementEvent = events[typeEvent] || {}
+  for (const handlerKey in storeElementEvent) {
+    if (!Object.prototype.hasOwnProperty.call(storeElementEvent, handlerKey)) {
+      continue
     }
-  }
 
-  return {
-    on(element, event, handler, delegationFn) {
-      addHandler(element, event, handler, delegationFn, false)
-    },
+    if (handlerKey.indexOf(namespace) > -1) {
+      const event = storeElementEvent[handlerKey]
+      removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
+    }
+  }
+}
 
-    one(element, event, handler, delegationFn) {
-      addHandler(element, event, handler, delegationFn, true)
-    },
+const EventHandler = {
+  on(element, event, handler, delegationFn) {
+    addHandler(element, event, handler, delegationFn, false)
+  },
 
-    off(element, originalTypeEvent, handler, delegationFn) {
-      if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) {
-        return
-      }
+  one(element, event, handler, delegationFn) {
+    addHandler(element, event, handler, delegationFn, true)
+  },
 
-      const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
+  off(element, originalTypeEvent, handler, delegationFn) {
+    if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) {
+      return
+    }
 
-      const inNamespace = typeEvent !== originalTypeEvent
-      const events = getEvent(element)
+    const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
 
-      if (typeof originalHandler !== 'undefined') {
-        // Simplest case: handler is passed, remove that listener ONLY.
-        if (!events || !events[typeEvent]) {
-          return
-        }
+    const inNamespace = typeEvent !== originalTypeEvent
+    const events = getEvent(element)
 
-        removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)
+    if (typeof originalHandler !== 'undefined') {
+      // Simplest case: handler is passed, remove that listener ONLY.
+      if (!events || !events[typeEvent]) {
         return
       }
 
-      const isNamespace = originalTypeEvent.charAt(0) === '.'
-      if (isNamespace) {
-        for (const elementEvent in events) {
-          if (!Object.prototype.hasOwnProperty.call(events, elementEvent)) {
-            continue
-          }
-
-          removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.substr(1))
-        }
-      }
+      removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)
+      return
+    }
 
-      const storeElementEvent = events[typeEvent] || {}
-      for (const keyHandlers in storeElementEvent) {
-        if (!Object.prototype.hasOwnProperty.call(storeElementEvent, keyHandlers)) {
+    const isNamespace = originalTypeEvent.charAt(0) === '.'
+    if (isNamespace) {
+      for (const elementEvent in events) {
+        if (!Object.prototype.hasOwnProperty.call(events, elementEvent)) {
           continue
         }
 
-        const handlerKey = keyHandlers.replace(stripUidRegex, '')
-        if (!inNamespace || originalTypeEvent.indexOf(handlerKey) > -1) {
-          const event = storeElementEvent[keyHandlers]
-          removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
-        }
+        removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.substr(1))
       }
-    },
+    }
 
-    trigger(element, event, args) {
-      if (typeof event !== 'string' ||
-          (typeof element === 'undefined' || element === null)) {
-        return null
+    const storeElementEvent = events[typeEvent] || {}
+    for (const keyHandlers in storeElementEvent) {
+      if (!Object.prototype.hasOwnProperty.call(storeElementEvent, keyHandlers)) {
+        continue
       }
 
-      const typeEvent   = event.replace(stripNameRegex, '')
-      const inNamespace = event !== typeEvent
-      const isNative    = nativeEvents.indexOf(typeEvent) > -1
+      const handlerKey = keyHandlers.replace(stripUidRegex, '')
+      if (!inNamespace || originalTypeEvent.indexOf(handlerKey) > -1) {
+        const event = storeElementEvent[keyHandlers]
+        removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
+      }
+    }
+  },
 
-      const $ = Util.jQuery
-      let jQueryEvent
+  trigger(element, event, args) {
+    if (typeof event !== 'string' ||
+        (typeof element === 'undefined' || element === null)) {
+      return null
+    }
 
-      let bubbles = true
-      let nativeDispatch = true
-      let defaultPrevented = false
+    const typeEvent   = event.replace(stripNameRegex, '')
+    const inNamespace = event !== typeEvent
+    const isNative    = nativeEvents.indexOf(typeEvent) > -1
 
-      if (inNamespace && typeof $ !== 'undefined') {
-        jQueryEvent = $.Event(event, args)
+    const $ = Util.jQuery
+    let jQueryEvent
 
-        $(element).trigger(jQueryEvent)
-        bubbles = !jQueryEvent.isPropagationStopped()
-        nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()
-        defaultPrevented = jQueryEvent.isDefaultPrevented()
-      }
+    let bubbles = true
+    let nativeDispatch = true
+    let defaultPrevented = false
 
-      let evt = null
-      if (isNative) {
-        evt = document.createEvent('HTMLEvents')
-        evt.initEvent(typeEvent, bubbles, true)
-      } else {
-        evt = new CustomEvent(event, {
-          bubbles,
-          cancelable: true
-        })
-      }
+    if (inNamespace && typeof $ !== 'undefined') {
+      jQueryEvent = $.Event(event, args)
 
-      // merge custom informations in our event
-      if (typeof args !== 'undefined') {
-        Object.keys(args)
-          .forEach((key) => {
-            Object.defineProperty(evt, key, {
-              get() {
-                return args[key]
-              }
-            })
-          })
-      }
+      $(element).trigger(jQueryEvent)
+      bubbles = !jQueryEvent.isPropagationStopped()
+      nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()
+      defaultPrevented = jQueryEvent.isDefaultPrevented()
+    }
 
-      if (defaultPrevented) {
-        evt.preventDefault()
+    let evt = null
+    if (isNative) {
+      evt = document.createEvent('HTMLEvents')
+      evt.initEvent(typeEvent, bubbles, true)
+    } else {
+      evt = new CustomEvent(event, {
+        bubbles,
+        cancelable: true
+      })
+    }
 
-        if (!Polyfill.defaultPreventedPreservedOnDispatch) {
-          Object.defineProperty(evt, 'defaultPrevented', {
-            get: () => 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 (nativeDispatch) {
-        element.dispatchEvent(evt)
-      }
+    if (defaultPrevented) {
+      evt.preventDefault()
 
-      if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {
-        jQueryEvent.preventDefault()
+      if (!Polyfill.defaultPreventedPreservedOnDispatch) {
+        Object.defineProperty(evt, 'defaultPrevented', {
+          get: () => true
+        })
       }
+    }
+
+    if (nativeDispatch) {
+      element.dispatchEvent(evt)
+    }
 
-      return evt
+    if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {
+      jQueryEvent.preventDefault()
     }
+
+    return evt
   }
-})()
+}
 
 /* istanbul ignore next */
 // focusin and focusout polyfill
index 45defb76e74b1e2015557b79ff9cee3e04da6bef..ff7ba1d1792cf949232c3858a384cdbae07e180d 100644 (file)
@@ -77,8 +77,9 @@ const Polyfill = (() => {
   }
 
   // matches polyfill (see: https://mzl.la/2ikXneG)
-  if (!Element.prototype.matches) {
-    Element.prototype.matches =
+  let matches = Element.prototype.matches
+  if (!matches) {
+    matches =
       Element.prototype.msMatchesSelector ||
       Element.prototype.webkitMatchesSelector
   }
@@ -86,15 +87,16 @@ const Polyfill = (() => {
   // closest polyfill (see: https://mzl.la/2vXggaI)
   let closest
   if (!Element.prototype.closest) {
+    const nodeText = 3
     closest = (element, selector) => {
       let ancestor = element
       do {
-        if (ancestor.matches(selector)) {
+        if (matches.call(ancestor, selector)) {
           return ancestor
         }
 
         ancestor = ancestor.parentElement
-      } while (ancestor !== null && ancestor.nodeType === Node.ELEMENT_NODE)
+      } while (ancestor !== null && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== nodeText)
 
       return null
     }
@@ -186,6 +188,7 @@ const Polyfill = (() => {
     defaultPreventedPreservedOnDispatch,
     focusIn: typeof window.onfocusin === 'undefined',
     closest,
+    matches,
     find,
     findOne
   }
index c2eec95a7eb73a64fa47031b867b536b466dcfc2..ee2f004850a983c9901132afd8d667b3507b0213 100644 (file)
@@ -8,93 +8,93 @@ import Util from '../util'
  * --------------------------------------------------------------------------
  */
 
-const SelectorEngine = (() => {
-  /**
-   * ------------------------------------------------------------------------
-   * Constants
-   * ------------------------------------------------------------------------
-   */
-
-  const closest = Polyfill.closest
-  const find = Polyfill.find
-  const findOne = Polyfill.findOne
-
-  return {
-    matches(element, selector) {
-      return element.matches(selector)
-    },
-
-    find(selector, element = document.documentElement) {
-      if (typeof selector !== 'string') {
-        return null
-      }
+/**
+ * ------------------------------------------------------------------------
+ * Constants
+ * ------------------------------------------------------------------------
+ */
 
-      return find.call(element, selector)
-    },
+const closest = Polyfill.closest
+const matchesFn = Polyfill.matches
+const find = Polyfill.find
+const findOne = Polyfill.findOne
+const nodeText = 3
 
-    findOne(selector, element = document.documentElement) {
-      if (typeof selector !== 'string') {
-        return null
-      }
+const SelectorEngine = {
+  matches(element, selector) {
+    return matchesFn.call(element, selector)
+  },
 
-      return findOne.call(element, selector)
-    },
+  find(selector, element = document.documentElement) {
+    if (typeof selector !== 'string') {
+      return null
+    }
 
-    children(element, selector) {
-      if (typeof selector !== 'string') {
-        return null
-      }
+    return find.call(element, selector)
+  },
+
+  findOne(selector, element = document.documentElement) {
+    if (typeof selector !== 'string') {
+      return null
+    }
 
-      const children = Util.makeArray(element.children)
-      return children.filter((child) => this.matches(child, selector))
-    },
+    return findOne.call(element, selector)
+  },
 
-    parents(element, selector) {
-      if (typeof selector !== 'string') {
-        return null
-      }
+  children(element, selector) {
+    if (typeof selector !== 'string') {
+      return null
+    }
+
+    const children = Util.makeArray(element.children)
+    return children.filter((child) => this.matches(child, selector))
+  },
 
-      const parents = []
+  parents(element, selector) {
+    if (typeof selector !== 'string') {
+      return null
+    }
 
-      let ancestor = element.parentNode
-      while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE) {
-        if (ancestor.matches(selector)) {
-          parents.push(ancestor)
-        }
+    const parents = []
 
-        ancestor = ancestor.parentNode
+    let ancestor = element.parentNode
+    while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== nodeText) {
+      if (this.matches(ancestor, selector)) {
+        parents.push(ancestor)
       }
 
-      return parents
-    },
+      ancestor = ancestor.parentNode
+    }
 
-    closest(element, selector) {
-      if (typeof selector !== 'string') {
-        return null
-      }
+    return parents
+  },
 
-      return closest(element, selector)
-    },
+  closest(element, selector) {
+    if (typeof selector !== 'string') {
+      return null
+    }
 
-    prev(element, selector) {
-      if (typeof selector !== 'string') {
-        return null
-      }
+    return closest(element, selector)
+  },
 
-      const siblings = []
+  prev(element, selector) {
+    if (typeof selector !== 'string') {
+      return null
+    }
 
-      let previous = element.previousSibling
-      while (previous) {
-        if (previous.matches(selector)) {
-          siblings.push(previous)
-        }
+    const siblings = []
 
-        previous = previous.previousSibling
+    let previous = element.previousSibling
+    while (previous && previous.nodeType === Node.ELEMENT_NODE && previous.nodeType !== nodeText) {
+      if (this.matches(previous, selector)) {
+        siblings.push(previous)
       }
 
-      return siblings
+      previous = previous.previousSibling
     }
+
+    return siblings
   }
-})()
+}
 
 export default SelectorEngine
index 9a22a69916e319a670b25995ee4324c433f5093e..8f3f8dd8957ae0c92d696d9f9ef5c9491f9e89c6 100644 (file)
@@ -313,7 +313,7 @@ class Dropdown {
   }
 
   _detectNavbar() {
-    return Util.makeArray(SelectorEngine.closest(this._element, '.navbar')).length > 0
+    return Boolean(SelectorEngine.closest(this._element, '.navbar'))
   }
 
   _getOffset() {
@@ -367,7 +367,6 @@ class Dropdown {
 
     if (!data) {
       data = new Dropdown(element, _config)
-      Data.setData(element, DATA_KEY, data)
     }
 
     if (typeof config === 'string') {
index 8e56fc5b29dc63dc76a0eb087f0703c409ca0b31..e2d61621d32efbc4ce96d58a05ac26d4c4ed9f15 100644 (file)
@@ -394,9 +394,8 @@ class Modal {
 
       if (this._element.classList.contains(ClassName.FADE)) {
         const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)
-
         EventHandler.one(this._backdrop, Util.TRANSITION_END, callbackRemove)
-        Util.emulateTransitionEnd(backdropTransitionDuration)
+        Util.emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
       } else {
         callbackRemove()
       }
index 93880bb8e402e9dcc0fdd316bf2690582da96150..43c11aa1d92abba5af586effd2e62c03b7756fb1 100644 (file)
@@ -127,7 +127,7 @@ class Tooltip {
      * Popper - https://popper.js.org
      */
     if (typeof Popper === 'undefined') {
-      throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org/)')
+      throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org)')
     }
 
     // private
@@ -201,7 +201,7 @@ class Tooltip {
 
       if (!context) {
         context = new this.constructor(
-          event.currentTarget,
+          event.delegateTarget,
           this._getDelegateConfig()
         )
         Data.setData(event.delegateTarget, dataKey, context)
@@ -344,7 +344,6 @@ class Tooltip {
 
       if (this.tip.classList.contains(ClassName.FADE)) {
         const transitionDuration = Util.getTransitionDurationFromElement(this.tip)
-
         EventHandler.one(this.tip, Util.TRANSITION_END, complete)
         Util.emulateTransitionEnd(this.tip, transitionDuration)
       } else {
@@ -383,7 +382,7 @@ class Tooltip {
     // empty mouseover listeners we added for iOS support
     if ('ontouchstart' in document.documentElement) {
       Util.makeArray(document.body.children)
-        .forEach((element) => EventHandler.off(element, 'mouseover', Util.noop()))
+        .forEach((element) => EventHandler.off(element, 'mouseover', Util.noop))
     }
 
     this._activeTrigger[Trigger.CLICK] = false
@@ -392,7 +391,6 @@ class Tooltip {
 
     if (this.tip.classList.contains(ClassName.FADE)) {
       const transitionDuration = Util.getTransitionDurationFromElement(tip)
-
       EventHandler.one(tip, Util.TRANSITION_END, complete)
       Util.emulateTransitionEnd(tip, transitionDuration)
     } else {
@@ -754,11 +752,9 @@ class Tooltip {
   _fixTransition() {
     const tip = this.getTipElement()
     const initConfigAnimation = this.config.animation
-
     if (tip.getAttribute('x-placement') !== null) {
       return
     }
-
     tip.classList.remove(ClassName.FADE)
     this.config.animation = false
     this.hide()
index caa2e63485afd32baee2b308160c3591457f64fb..7183aeb6ba761edc56258c5f400d14e923a03c51 100644 (file)
@@ -11,7 +11,6 @@
  * ------------------------------------------------------------------------
  */
 
-const TRANSITION_END = 'transitionend'
 const MAX_UID = 1000000
 const MILLISECONDS_MULTIPLIER = 1000
 
@@ -20,9 +19,14 @@ function toType(obj) {
   return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase()
 }
 
-const Util = {
+/**
+ * --------------------------------------------------------------------------
+ * Public Util Api
+ * --------------------------------------------------------------------------
+ */
 
-  TRANSITION_END: 'bsTransitionEnd',
+const Util = {
+  TRANSITION_END: 'transitionend',
 
   getUID(prefix) {
     do {
@@ -79,19 +83,25 @@ const Util = {
     element.dispatchEvent(new Event(Util.TRANSITION_END))
   },
 
-  // TODO: Remove in v5
-  supportsTransitionEnd() {
-    return Boolean(TRANSITION_END)
-  },
-
   isElement(obj) {
     return (obj[0] || obj).nodeType
   },
 
   emulateTransitionEnd(element, duration) {
+    let called = false
+    const durationPadding = 5
+    const emulatedDuration = duration + durationPadding
+    function listener() {
+      called = true
+      element.removeEventListener(Util.TRANSITION_END, listener)
+    }
+
+    element.addEventListener(Util.TRANSITION_END, listener)
     setTimeout(() => {
-      Util.triggerTransitionEnd(element)
-    }, duration)
+      if (!called) {
+        Util.triggerTransitionEnd(element)
+      }
+    }, emulatedDuration)
   },
 
   typeCheckConfig(componentName, config, configTypes) {
@@ -117,13 +127,7 @@ const Util = {
       return []
     }
 
-    const strRepresentation = Object.prototype.toString.call(nodeList)
-    if (strRepresentation === '[object NodeList]' ||
-      strRepresentation === '[object HTMLCollection]' || strRepresentation === '[object Array]') {
-      return Array.prototype.slice.call(nodeList)
-    }
-
-    return [nodeList]
+    return [].slice.call(nodeList)
   },
 
   isVisible(element) {
index fa930dcac23ef5f7f922c8c66356153dbef263c7..db1412a3ba18e732ca0affa604e250d1edda3ecf 100644 (file)
@@ -119,11 +119,6 @@ $(function () {
     assert.ok(id !== id2, id + ' !== ' + id2)
   })
 
-  QUnit.test('Util.supportsTransitionEnd should return true', function (assert) {
-    assert.expect(1)
-    assert.ok(Util.supportsTransitionEnd())
-  })
-
   QUnit.test('Util.findShadowRoot should find the shadow DOM root', function (assert) {
     // Only for newer browsers
     if (!document.documentElement.attachShadow) {
@@ -178,4 +173,21 @@ $(function () {
 
     window.$ = jQuery
   })
+
+  QUnit.test('Util.emulateTransitionEnd should emulate transition end', function (assert) {
+    assert.expect(1)
+    var $div = $('<div></div>').appendTo($('#qunit-fixture'))
+
+    var spy = sinon.spy($div[0], 'removeEventListener')
+
+    Util.emulateTransitionEnd($div[0], 7)
+
+    assert.ok(spy.notCalled)
+  })
+
+  QUnit.test('Util.makeArray should return empty array on null', function (assert) {
+    assert.expect(1)
+
+    assert.ok(Util.makeArray(null).length === 0)
+  })
 })
index d039273d5f4abe8ce11d307e81eb75c3e126a171..3293fddab09190360fa24099c6e0faf5ef439407 100644 (file)
 
   document.addEventListener('DOMContentLoaded', function () {
     // Tooltip and popover demos
-    var tooltipDemoList = document.querySelectorAll('.tooltip-demo')
+    var tooltipDemoList = [].slice.call(document.querySelectorAll('.tooltip-demo'))
     tooltipDemoList.forEach(function (tooltip) {
       new bootstrap.Tooltip(tooltip, {
         selector: '[data-toggle="tooltip"]'
       })
     })
-    var popoverList = document.querySelectorAll('[data-toggle="popover"]')
+
+    var popoverList = [].slice.call(document.querySelectorAll('[data-toggle="popover"]'))
     popoverList.forEach(function (popover) {
       new bootstrap.Popover(popover)
     })
       .toast('show')
 
     // Demos within modals
-    var tooltipTestList = document.querySelectorAll('.tooltip-test')
+    var tooltipTestList = [].slice.call(document.querySelectorAll('.tooltip-test'))
     tooltipTestList.forEach(function (tooltip) {
       new bootstrap.Tooltip(tooltip)
     })
 
-    var popoverTestList = document.querySelectorAll('.popover-test')
+    var popoverTestList = [].slice.call(document.querySelectorAll('.popover-test'))
     popoverTestList.forEach(function (popover) {
       new bootstrap.Popover(popover)
     })
 
     // Indeterminate checkbox example
-    var indeterminateCheckboxList = document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')
+    var indeterminateCheckboxList = [].slice.call(document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]'))
     indeterminateCheckboxList.forEach(function (checkbox) {
       checkbox.indeterminate = true
     })
 
     // Disable empty links in docs examples
-    var emptyLinkList = document.querySelectorAll('.bd-content [href="#"]')
+    var emptyLinkList = [].slice.call(document.querySelectorAll('.bd-content [href="#"]'))
     emptyLinkList.forEach(function (link) {
       link.addEventListener('click', function (e) {
         e.preventDefault()
@@ -77,7 +78,7 @@
     }
 
     // Activate animated progress bar
-    var animatedProgressBarList = document.querySelectorAll('.bd-toggle-animated-progress > .progress-bar-striped')
+    var animatedProgressBarList = [].slice.call(document.querySelectorAll('.bd-toggle-animated-progress > .progress-bar-striped'))
     animatedProgressBarList.forEach(function (progressBar) {
       progressBar.addEventListener('click', function () {
         if (progressBar.classList.contains('progress-bar-animated')) {
@@ -89,9 +90,9 @@
     })
 
     // Insert copy to clipboard button before .highlight
-    var hightList = [].slice.call(document.querySelectorAll('figure.highlight, div.highlight'))
     var btnHtml = '<div class="bd-clipboard"><button type="button" class="btn-clipboard" title="Copy to clipboard">Copy</button></div>'
-    hightList.forEach(function (element) {
+    var highList = [].slice.call(document.querySelectorAll('figure.highlight, div.highlight'))
+    highList.forEach(function (element) {
       element.insertAdjacentHTML('beforebegin', btnHtml)
     })