]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
move util in a util folder with the sanitizer
authorJohann-S <johann.servoire@gmail.com>
Fri, 22 Feb 2019 22:37:55 +0000 (00:37 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Tue, 26 Feb 2019 11:04:04 +0000 (13:04 +0200)
34 files changed:
.eslintrc.json
build/build-plugins.js
js/src/alert.js
js/src/button.js
js/src/carousel.js
js/src/collapse.js
js/src/dom/eventHandler.js
js/src/dom/polyfill.js
js/src/dom/selectorEngine.js
js/src/dropdown.js
js/src/index.js
js/src/modal.js
js/src/popover.js
js/src/scrollspy.js
js/src/tab.js
js/src/toast.js
js/src/tooltip.js
js/src/util.js [deleted file]
js/src/util/index.js [new file with mode: 0644]
js/src/util/sanitizer.js [moved from js/src/tools/sanitizer.js with 89% similarity]
js/tests/index.html [deleted file]
js/tests/integration/bundle.js
js/tests/integration/index.html
js/tests/karma.conf.js
js/tests/unit/.eslintrc.json
js/tests/unit/modal.js
js/tests/unit/tooltip.js
js/tests/unit/util/index.js [moved from js/tests/unit/util.js with 98% similarity]
js/tests/unit/util/sanitizer.js [new file with mode: 0644]
js/tests/visual/toast.html
package.json
site/docs/4.3/assets/js/src/application.js
site/docs/4.3/getting-started/javascript.md
site/docs/4.3/getting-started/webpack.md

index e0b3e5380ac2525cec5bd5898eff7132f0119d33..8250d274c1fdf794b0428c2df879b6081c262cb9 100644 (file)
     "func-call-spacing": "error",
     "func-name-matching": "error",
     "func-names": "off",
-    "func-style": ["error", "declaration"],
+    "func-style": "off",
     "id-blacklist": "error",
     "id-length": "off",
     "id-match": "error",
index cabd44e45592b13c61289d795afdc561920c161c..1339684963d6a5636664f92ab9f93c46e864412b 100644 (file)
@@ -41,23 +41,25 @@ const bsPlugins = {
   ScrollSpy: path.resolve(__dirname, '../js/src/scrollspy.js'),
   Tab: path.resolve(__dirname, '../js/src/tab.js'),
   Toast: path.resolve(__dirname, '../js/src/toast.js'),
-  Tooltip: path.resolve(__dirname, '../js/src/tooltip.js'),
-  Util: path.resolve(__dirname, '../js/src/util.js')
+  Tooltip: path.resolve(__dirname, '../js/src/tooltip.js')
 }
 const rootPath = TEST ? '../js/coverage/dist/' : '../js/dist/'
 
+if (TEST) {
+  bsPlugins.Util = path.resolve(__dirname, '../js/src/util/index.js')
+  bsPlugins.Sanitizer = path.resolve(__dirname, '../js/src/util/sanitizer.js')
+}
+
 const defaultPluginConfig = {
   external: [
     bsPlugins.Data,
     bsPlugins.EventHandler,
-    bsPlugins.SelectorEngine,
-    bsPlugins.Util
+    bsPlugins.SelectorEngine
   ],
   globals: {
     [bsPlugins.Data]: 'Data',
     [bsPlugins.EventHandler]: 'EventHandler',
-    [bsPlugins.SelectorEngine]: 'SelectorEngine',
-    [bsPlugins.Util]: 'Util'
+    [bsPlugins.SelectorEngine]: 'SelectorEngine'
   }
 }
 
@@ -65,7 +67,9 @@ function getConfigByPluginKey(pluginKey) {
   if (
     pluginKey === 'Data' ||
     pluginKey === 'Manipulator' ||
-    pluginKey === 'Util'
+    pluginKey === 'Polyfill' ||
+    pluginKey === 'Util' ||
+    pluginKey === 'Sanitizer'
   ) {
     return {
       external: [],
@@ -76,21 +80,10 @@ function getConfigByPluginKey(pluginKey) {
   if (pluginKey === 'EventHandler' || pluginKey === 'SelectorEngine') {
     return {
       external: [
-        bsPlugins.Polyfill,
-        bsPlugins.Util
+        bsPlugins.Polyfill
       ],
       globals: {
-        [bsPlugins.Polyfill]: 'Polyfill',
-        [bsPlugins.Util]: 'Util'
-      }
-    }
-  }
-
-  if (pluginKey === 'Polyfill') {
-    return {
-      external: [bsPlugins.Util],
-      globals: {
-        [bsPlugins.Util]: 'Util'
+        [bsPlugins.Polyfill]: 'Polyfill'
       }
     }
   }
@@ -125,14 +118,12 @@ function getConfigByPluginKey(pluginKey) {
       external: [
         bsPlugins.Data,
         bsPlugins.SelectorEngine,
-        bsPlugins.Tooltip,
-        bsPlugins.Util
+        bsPlugins.Tooltip
       ],
       globals: {
         [bsPlugins.Data]: 'Data',
         [bsPlugins.SelectorEngine]: 'SelectorEngine',
-        [bsPlugins.Tooltip]: 'Tooltip',
-        [bsPlugins.Util]: 'Util'
+        [bsPlugins.Tooltip]: 'Tooltip'
       }
     }
   }
@@ -142,14 +133,12 @@ function getConfigByPluginKey(pluginKey) {
       external: [
         bsPlugins.Data,
         bsPlugins.EventHandler,
-        bsPlugins.Manipulator,
-        bsPlugins.Util
+        bsPlugins.Manipulator
       ],
       globals: {
         [bsPlugins.Data]: 'Data',
         [bsPlugins.EventHandler]: 'EventHandler',
-        [bsPlugins.Manipulator]: 'Manipulator',
-        [bsPlugins.Util]: 'Util'
+        [bsPlugins.Manipulator]: 'Manipulator'
       }
     }
   }
@@ -161,14 +150,28 @@ function build(plugin) {
   const config = getConfigByPluginKey(plugin)
   const external = config.external
   const globals = config.globals
+  let pluginPath = rootPath
+
+  const utilObjects = [
+    'Util',
+    'Sanitizer'
+  ]
 
-  const pluginPath = [
+  const domObjects = [
     'Data',
     'EventHandler',
     'Manipulator',
     'Polyfill',
     'SelectorEngine'
-  ].includes(plugin) ? `${rootPath}/dom/` : rootPath
+  ]
+
+  if (utilObjects.includes(plugin)) {
+    pluginPath = `${rootPath}/util/`
+  }
+
+  if (domObjects.includes(plugin)) {
+    pluginPath = `${rootPath}/dom/`
+  }
 
   const pluginFilename = `${plugin.toLowerCase()}.js`
 
index a8d0cc5da99b9acedf4788b1a62af6d708920d30..9d8f69c465974882c0540104b10289e6bfc8473c 100644 (file)
@@ -5,10 +5,16 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  getSelectorFromElement,
+  getTransitionDurationFromElement
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -83,7 +89,7 @@ class Alert {
   // Private
 
   _getRootElement(element) {
-    const selector = Util.getSelectorFromElement(element)
+    const selector = getSelectorFromElement(element)
     let parent     = false
 
     if (selector) {
@@ -109,11 +115,11 @@ class Alert {
       return
     }
 
-    const transitionDuration = Util.getTransitionDurationFromElement(element)
+    const transitionDuration = getTransitionDurationFromElement(element)
 
     EventHandler
-      .one(element, Util.TRANSITION_END, (event) => this._destroyElement(element, event))
-    Util.emulateTransitionEnd(element, transitionDuration)
+      .one(element, TRANSITION_END, (event) => this._destroyElement(element, event))
+    emulateTransitionEnd(element, transitionDuration)
   }
 
   _destroyElement(element) {
@@ -170,7 +176,6 @@ EventHandler
  * add .alert to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Alert._jQueryInterface
index 2cb6acabaea4dbdb22ac3d7d56f82f9624079eca..489fe9de35906c9b402af1a58ae0639e124b973f 100644 (file)
@@ -5,10 +5,12 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -180,7 +182,6 @@ EventHandler.on(document, Event.BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, (eve
  * add .button to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT  = $.fn[NAME]
   $.fn[NAME]                = Button._jQueryInterface
index 053659314164c194eeb174f1a0d7f66c2067e0a2..d253d51dfde27cae8fb43605dbb468dc63ceeefc 100644 (file)
@@ -5,11 +5,22 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  getSelectorFromElement,
+  getTransitionDurationFromElement,
+  isVisible,
+  makeArray,
+  reflow,
+  triggerTransitionEnd,
+  typeCheckConfig
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -143,7 +154,7 @@ class Carousel {
   nextWhenVisible() {
     // Don't call next when the page isn't visible
     // or the carousel or its parent isn't visible
-    if (!document.hidden && Util.isVisible(this._element)) {
+    if (!document.hidden && isVisible(this._element)) {
       this.next()
     }
   }
@@ -160,7 +171,7 @@ class Carousel {
     }
 
     if (SelectorEngine.findOne(Selector.NEXT_PREV, this._element)) {
-      Util.triggerTransitionEnd(this._element)
+      triggerTransitionEnd(this._element)
       this.cycle(true)
     }
 
@@ -233,7 +244,7 @@ class Carousel {
       ...Default,
       ...config
     }
-    Util.typeCheckConfig(NAME, config, DefaultType)
+    typeCheckConfig(NAME, config, DefaultType)
     return config
   }
 
@@ -320,7 +331,7 @@ class Carousel {
       }
     }
 
-    Util.makeArray(SelectorEngine.find(Selector.ITEM_IMG, this._element)).forEach((itemImg) => {
+    makeArray(SelectorEngine.find(Selector.ITEM_IMG, this._element)).forEach((itemImg) => {
       EventHandler.on(itemImg, Event.DRAG_START, (e) => e.preventDefault())
     })
 
@@ -356,7 +367,7 @@ class Carousel {
 
   _getItemIndex(element) {
     this._items = element && element.parentNode
-      ? Util.makeArray(SelectorEngine.find(Selector.ITEM, element.parentNode))
+      ? makeArray(SelectorEngine.find(Selector.ITEM, element.parentNode))
       : []
 
     return this._items.indexOf(element)
@@ -459,7 +470,7 @@ class Carousel {
     if (this._element.classList.contains(ClassName.SLIDE)) {
       nextElement.classList.add(orderClassName)
 
-      Util.reflow(nextElement)
+      reflow(nextElement)
 
       activeElement.classList.add(directionalClassName)
       nextElement.classList.add(directionalClassName)
@@ -472,10 +483,10 @@ class Carousel {
         this._config.interval = this._config.defaultInterval || this._config.interval
       }
 
-      const transitionDuration = Util.getTransitionDurationFromElement(activeElement)
+      const transitionDuration = getTransitionDurationFromElement(activeElement)
 
       EventHandler
-        .one(activeElement, Util.TRANSITION_END, () => {
+        .one(activeElement, TRANSITION_END, () => {
           nextElement.classList.remove(directionalClassName)
           nextElement.classList.remove(orderClassName)
           nextElement.classList.add(ClassName.ACTIVE)
@@ -496,7 +507,7 @@ class Carousel {
           }, 0)
         })
 
-      Util.emulateTransitionEnd(activeElement, transitionDuration)
+      emulateTransitionEnd(activeElement, transitionDuration)
     } else {
       activeElement.classList.remove(ClassName.ACTIVE)
       nextElement.classList.add(ClassName.ACTIVE)
@@ -557,7 +568,7 @@ class Carousel {
   }
 
   static _dataApiClickHandler(event) {
-    const selector = Util.getSelectorFromElement(this)
+    const selector = getSelectorFromElement(this)
 
     if (!selector) {
       return
@@ -603,7 +614,7 @@ EventHandler
   .on(document, Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler)
 
 EventHandler.on(window, Event.LOAD_DATA_API, () => {
-  const carousels = Util.makeArray(SelectorEngine.find(Selector.DATA_RIDE))
+  const carousels = makeArray(SelectorEngine.find(Selector.DATA_RIDE))
   for (let i = 0, len = carousels.length; i < len; i++) {
     Carousel._carouselInterface(carousels[i], Data.getData(carousels[i], DATA_KEY))
   }
@@ -616,7 +627,6 @@ EventHandler.on(window, Event.LOAD_DATA_API, () => {
  * add .carousel to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Carousel._jQueryInterface
index 9c2773754ccb6320904fb0c9bccdad69e15ad925..847062d8d1d8b35fb2c15d3b96915e1e59aafaa9 100644 (file)
@@ -5,11 +5,21 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  getSelectorFromElement,
+  getTransitionDurationFromElement,
+  isElement,
+  makeArray,
+  reflow,
+  typeCheckConfig
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -69,16 +79,16 @@ class Collapse {
     this._isTransitioning = false
     this._element         = element
     this._config          = this._getConfig(config)
-    this._triggerArray    = Util.makeArray(SelectorEngine.find(
+    this._triggerArray    = makeArray(SelectorEngine.find(
       `[data-toggle="collapse"][href="#${element.id}"],` +
       `[data-toggle="collapse"][data-target="#${element.id}"]`
     ))
 
-    const toggleList = Util.makeArray(SelectorEngine.find(Selector.DATA_TOGGLE))
+    const toggleList = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE))
     for (let i = 0, len = toggleList.length; i < len; i++) {
       const elem = toggleList[i]
-      const selector = Util.getSelectorFromElement(elem)
-      const filterElement = Util.makeArray(SelectorEngine.find(selector))
+      const selector = getSelectorFromElement(elem)
+      const filterElement = makeArray(SelectorEngine.find(selector))
         .filter((foundElem) => foundElem === element)
 
       if (selector !== null && filterElement.length) {
@@ -130,7 +140,7 @@ class Collapse {
     let activesData
 
     if (this._parent) {
-      actives = Util.makeArray(SelectorEngine.find(Selector.ACTIVES, this._parent))
+      actives = makeArray(SelectorEngine.find(Selector.ACTIVES, this._parent))
         .filter((elem) => {
           if (typeof this._config.parent === 'string') {
             return elem.getAttribute('data-parent') === this._config.parent
@@ -201,11 +211,11 @@ class Collapse {
 
     const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
     const scrollSize = `scroll${capitalizedDimension}`
-    const transitionDuration = Util.getTransitionDurationFromElement(this._element)
+    const transitionDuration = getTransitionDurationFromElement(this._element)
 
-    EventHandler.one(this._element, Util.TRANSITION_END, complete)
+    EventHandler.one(this._element, TRANSITION_END, complete)
 
-    Util.emulateTransitionEnd(this._element, transitionDuration)
+    emulateTransitionEnd(this._element, transitionDuration)
     this._element.style[dimension] = `${this._element[scrollSize]}px`
   }
 
@@ -224,7 +234,7 @@ class Collapse {
 
     this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`
 
-    Util.reflow(this._element)
+    reflow(this._element)
 
     this._element.classList.add(ClassName.COLLAPSING)
     this._element.classList.remove(ClassName.COLLAPSE)
@@ -234,7 +244,7 @@ class Collapse {
     if (triggerArrayLength > 0) {
       for (let i = 0; i < triggerArrayLength; i++) {
         const trigger = this._triggerArray[i]
-        const selector = Util.getSelectorFromElement(trigger)
+        const selector = getSelectorFromElement(trigger)
 
         if (selector !== null) {
           const elem = SelectorEngine.findOne(selector)
@@ -257,10 +267,10 @@ class Collapse {
     }
 
     this._element.style[dimension] = ''
-    const transitionDuration = Util.getTransitionDurationFromElement(this._element)
+    const transitionDuration = getTransitionDurationFromElement(this._element)
 
-    EventHandler.one(this._element, Util.TRANSITION_END, complete)
-    Util.emulateTransitionEnd(this._element, transitionDuration)
+    EventHandler.one(this._element, TRANSITION_END, complete)
+    emulateTransitionEnd(this._element, transitionDuration)
   }
 
   setTransitioning(isTransitioning) {
@@ -285,7 +295,7 @@ class Collapse {
       ...config
     }
     config.toggle = Boolean(config.toggle) // Coerce string values
-    Util.typeCheckConfig(NAME, config, DefaultType)
+    typeCheckConfig(NAME, config, DefaultType)
     return config
   }
 
@@ -297,7 +307,7 @@ class Collapse {
   _getParent() {
     let parent
 
-    if (Util.isElement(this._config.parent)) {
+    if (isElement(this._config.parent)) {
       parent = this._config.parent
 
       // it's a jQuery object
@@ -311,7 +321,7 @@ class Collapse {
     const selector =
       `[data-toggle="collapse"][data-parent="${this._config.parent}"]`
 
-    Util.makeArray(SelectorEngine.find(selector, parent))
+    makeArray(SelectorEngine.find(selector, parent))
       .forEach((element) => {
         this._addAriaAndCollapsedClass(
           Collapse._getTargetFromElement(element),
@@ -342,7 +352,7 @@ class Collapse {
   // Static
 
   static _getTargetFromElement(element) {
-    const selector = Util.getSelectorFromElement(element)
+    const selector = getSelectorFromElement(element)
     return selector ? SelectorEngine.findOne(selector) : null
   }
 
@@ -394,8 +404,8 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (
   }
 
   const triggerData      = Manipulator.getDataAttributes(this)
-  const selector         = Util.getSelectorFromElement(this)
-  const selectorElements = Util.makeArray(SelectorEngine.find(selector))
+  const selector         = getSelectorFromElement(this)
+  const selectorElements = makeArray(SelectorEngine.find(selector))
 
   selectorElements.forEach((element) => {
     const data = Data.getData(element, DATA_KEY)
@@ -422,7 +432,6 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (
  * add .collapse to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT  = $.fn[NAME]
   $.fn[NAME]                = Collapse._jQueryInterface
index 3b42388b8ebe8754c4e76aacf41e2524f4421076..d92920cdaac2f9d59c386fc82f709e8f1caabcec 100644 (file)
@@ -5,8 +5,10 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $
+} from '../util/index'
 import Polyfill from './polyfill'
-import Util from '../util'
 
 /**
  * ------------------------------------------------------------------------
@@ -248,7 +250,6 @@ const EventHandler = {
     const typeEvent   = event.replace(stripNameRegex, '')
     const inNamespace = event !== typeEvent
     const isNative    = nativeEvents.indexOf(typeEvent) > -1
-    const $ = Util.jQuery
 
     let jQueryEvent
     let bubbles = true
index cbae0f668e1dc0a4b0018ff46e259e87d282cc6a..579b7a10ed762f7e5dc68e0e71d172ae72f08457 100644 (file)
@@ -5,7 +5,9 @@
  * --------------------------------------------------------------------------
  */
 
-import Util from '../util'
+import {
+  getUID
+} from '../util/index'
 
 /* istanbul ignore next */
 const Polyfill = (() => {
@@ -48,7 +50,7 @@ const Polyfill = (() => {
       const hasId = Boolean(this.id)
 
       if (!hasId) {
-        this.id = Util.getUID('scope')
+        this.id = getUID('scope')
       }
 
       let nodeList = null
index 95b5a9fb50eec65f84ea92c014f40dfaa0f21ed2..151bb54c7553f4219053d471bd6f0ec51d925b51 100644 (file)
@@ -6,7 +6,9 @@
  */
 
 import Polyfill from './polyfill'
-import Util from '../util'
+import {
+  makeArray
+} from '../util/index'
 
 /**
  * ------------------------------------------------------------------------
@@ -44,7 +46,7 @@ const SelectorEngine = {
       return null
     }
 
-    const children = Util.makeArray(element.children)
+    const children = makeArray(element.children)
 
     return children.filter((child) => this.matches(child, selector))
   },
index 46aff8f884d84abf9b8750e9b565b2ab959ce632..f5150a016fbd2fabeee6634eb83e0fb4fdc2d5cc 100644 (file)
@@ -5,12 +5,19 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  getSelectorFromElement,
+  isElement,
+  makeArray,
+  noop,
+  typeCheckConfig
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
 import Popper from 'popper.js'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -159,7 +166,7 @@ class Dropdown {
 
       if (this._config.reference === 'parent') {
         referenceElement = parent
-      } else if (Util.isElement(this._config.reference)) {
+      } else if (isElement(this._config.reference)) {
         referenceElement = this._config.reference
 
         // Check if it's jQuery element
@@ -182,9 +189,9 @@ class Dropdown {
     // only needed because of broken event delegation on iOS
     // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
     if ('ontouchstart' in document.documentElement &&
-      !Util.makeArray(SelectorEngine.closest(parent, Selector.NAVBAR_NAV)).length) {
-      Util.makeArray(document.body.children)
-        .forEach((elem) => EventHandler.on(elem, 'mouseover', null, Util.noop()))
+      !makeArray(SelectorEngine.closest(parent, Selector.NAVBAR_NAV)).length) {
+      makeArray(document.body.children)
+        .forEach((elem) => EventHandler.on(elem, 'mouseover', null, noop()))
     }
 
     this._element.focus()
@@ -272,7 +279,7 @@ class Dropdown {
       ...config
     }
 
-    Util.typeCheckConfig(
+    typeCheckConfig(
       NAME,
       config,
       this.constructor.DefaultType
@@ -389,7 +396,7 @@ class Dropdown {
       return
     }
 
-    const toggles = Util.makeArray(SelectorEngine.find(Selector.DATA_TOGGLE))
+    const toggles = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE))
     for (let i = 0, len = toggles.length; i < len; i++) {
       const parent        = Dropdown._getParentFromElement(toggles[i])
       const context       = Data.getData(toggles[i], DATA_KEY)
@@ -425,8 +432,8 @@ class Dropdown {
       // If this is a touch-enabled device we remove the extra
       // empty mouseover listeners we added for iOS support
       if ('ontouchstart' in document.documentElement) {
-        Util.makeArray(document.body.children)
-          .forEach((elem) => EventHandler.off(elem, 'mouseover', null, Util.noop()))
+        makeArray(document.body.children)
+          .forEach((elem) => EventHandler.off(elem, 'mouseover', null, noop()))
       }
 
       toggles[i].setAttribute('aria-expanded', 'false')
@@ -439,7 +446,7 @@ class Dropdown {
 
   static _getParentFromElement(element) {
     let parent
-    const selector = Util.getSelectorFromElement(element)
+    const selector = getSelectorFromElement(element)
 
     if (selector) {
       parent = SelectorEngine.findOne(selector)
@@ -482,7 +489,7 @@ class Dropdown {
       return
     }
 
-    const items = Util.makeArray(SelectorEngine.find(Selector.VISIBLE_ITEMS, parent))
+    const items = makeArray(SelectorEngine.find(Selector.VISIBLE_ITEMS, parent))
 
     if (!items.length) {
       return
@@ -535,7 +542,6 @@ EventHandler
  * add .dropdown to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Dropdown._jQueryInterface
index 8a0044fe53d3383b515cb27797b4c0d51f585b07..4d46d84b9526d06ba321ed430c27cabfc5e96b88 100644 (file)
@@ -16,10 +16,8 @@ import ScrollSpy from './scrollspy'
 import Tab from './tab'
 import Toast from './toast'
 import Tooltip from './tooltip'
-import Util from './util'
 
 export {
-  Util,
   Alert,
   Button,
   Carousel,
index 0897b317ab61a98bb6df0c9ce31a0f669d9bd4a3..7efb24f8422a8954425f027374e2272493c8a76e 100644 (file)
@@ -5,11 +5,21 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  getSelectorFromElement,
+  getTransitionDurationFromElement,
+  isVisible,
+  makeArray,
+  reflow,
+  typeCheckConfig
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -184,10 +194,10 @@ class Modal {
 
 
     if (transition) {
-      const transitionDuration  = Util.getTransitionDurationFromElement(this._element)
+      const transitionDuration getTransitionDurationFromElement(this._element)
 
-      EventHandler.one(this._element, Util.TRANSITION_END, (event) => this._hideModal(event))
-      Util.emulateTransitionEnd(this._element, transitionDuration)
+      EventHandler.one(this._element, TRANSITION_END, (event) => this._hideModal(event))
+      emulateTransitionEnd(this._element, transitionDuration)
     } else {
       this._hideModal()
     }
@@ -228,7 +238,7 @@ class Modal {
       ...Default,
       ...config
     }
-    Util.typeCheckConfig(NAME, config, DefaultType)
+    typeCheckConfig(NAME, config, DefaultType)
     return config
   }
 
@@ -252,7 +262,7 @@ class Modal {
     }
 
     if (transition) {
-      Util.reflow(this._element)
+      reflow(this._element)
     }
 
     this._element.classList.add(ClassName.SHOW)
@@ -272,10 +282,10 @@ class Modal {
     }
 
     if (transition) {
-      const transitionDuration  = Util.getTransitionDurationFromElement(this._dialog)
+      const transitionDuration  = getTransitionDurationFromElement(this._dialog)
 
-      EventHandler.one(this._dialog, Util.TRANSITION_END, transitionComplete)
-      Util.emulateTransitionEnd(this._dialog, transitionDuration)
+      EventHandler.one(this._dialog, TRANSITION_END, transitionComplete)
+      emulateTransitionEnd(this._dialog, transitionDuration)
     } else {
       transitionComplete()
     }
@@ -364,7 +374,7 @@ class Modal {
       })
 
       if (animate) {
-        Util.reflow(this._backdrop)
+        reflow(this._backdrop)
       }
 
       this._backdrop.classList.add(ClassName.SHOW)
@@ -378,10 +388,10 @@ class Modal {
         return
       }
 
-      const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)
+      const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
 
-      EventHandler.one(this._backdrop, Util.TRANSITION_END, callback)
-      Util.emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
+      EventHandler.one(this._backdrop, TRANSITION_END, callback)
+      emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
     } else if (!this._isShown && this._backdrop) {
       this._backdrop.classList.remove(ClassName.SHOW)
 
@@ -393,9 +403,9 @@ 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(this._backdrop, backdropTransitionDuration)
+        const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
+        EventHandler.one(this._backdrop, TRANSITION_END, callbackRemove)
+        emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
       } else {
         callbackRemove()
       }
@@ -439,7 +449,7 @@ class Modal {
       //   while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set
 
       // Adjust fixed content padding
-      Util.makeArray(SelectorEngine.find(Selector.FIXED_CONTENT))
+      makeArray(SelectorEngine.find(Selector.FIXED_CONTENT))
         .forEach((element) => {
           const actualPadding = element.style.paddingRight
           const calculatedPadding = window.getComputedStyle(element)['padding-right']
@@ -448,7 +458,7 @@ class Modal {
         })
 
       // Adjust sticky content margin
-      Util.makeArray(SelectorEngine.find(Selector.STICKY_CONTENT))
+      makeArray(SelectorEngine.find(Selector.STICKY_CONTENT))
         .forEach((element) => {
           const actualMargin = element.style.marginRight
           const calculatedMargin = window.getComputedStyle(element)['margin-right']
@@ -469,7 +479,7 @@ class Modal {
 
   _resetScrollbar() {
     // Restore fixed content padding
-    Util.makeArray(SelectorEngine.find(Selector.FIXED_CONTENT))
+    makeArray(SelectorEngine.find(Selector.FIXED_CONTENT))
       .forEach((element) => {
         const padding = Manipulator.getDataAttribute(element, 'padding-right')
         if (typeof padding !== 'undefined') {
@@ -479,7 +489,7 @@ class Modal {
       })
 
     // Restore sticky content and navbar-toggler margin
-    Util.makeArray(SelectorEngine.find(`${Selector.STICKY_CONTENT}`))
+    makeArray(SelectorEngine.find(`${Selector.STICKY_CONTENT}`))
       .forEach((element) => {
         const margin = Manipulator.getDataAttribute(element, 'margin-right')
         if (typeof margin !== 'undefined') {
@@ -546,7 +556,7 @@ class Modal {
 
 EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
   let target
-  const selector = Util.getSelectorFromElement(this)
+  const selector = getSelectorFromElement(this)
 
   if (selector) {
     target = SelectorEngine.findOne(selector)
@@ -569,7 +579,7 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (
     }
 
     EventHandler.one(target, Event.HIDDEN, () => {
-      if (Util.isVisible(this)) {
+      if (isVisible(this)) {
         this.focus()
       }
     })
@@ -589,7 +599,6 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (
  * ------------------------------------------------------------------------
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Modal._jQueryInterface
index ce5edcd5c4df5262cfa04ba3d893954c26023ce0..958beaa3d066e17b9fb21b1a05c65fbb271a80ef 100644 (file)
@@ -5,10 +5,12 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $
+} from './util/index'
 import Data from './dom/data'
 import SelectorEngine from './dom/selectorEngine'
 import Tooltip from './tooltip'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -177,7 +179,6 @@ class Popover extends Tooltip {
  * ------------------------------------------------------------------------
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Popover._jQueryInterface
index f5d1008af78d5bebc8a0c1269a404a41faed73e5..74b60b7348c19f9f48c24c5e55a1de6b4db6adcf 100644 (file)
@@ -5,11 +5,17 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  getSelectorFromElement,
+  getUID,
+  makeArray,
+  typeCheckConfig
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -118,12 +124,12 @@ class ScrollSpy {
 
     this._scrollHeight = this._getScrollHeight()
 
-    const targets = Util.makeArray(SelectorEngine.find(this._selector))
+    const targets = makeArray(SelectorEngine.find(this._selector))
 
     targets
       .map((element) => {
         let target
-        const targetSelector = Util.getSelectorFromElement(element)
+        const targetSelector = getSelectorFromElement(element)
 
         if (targetSelector) {
           target = SelectorEngine.findOne(targetSelector)
@@ -174,13 +180,13 @@ class ScrollSpy {
     if (typeof config.target !== 'string') {
       let id = config.target.id
       if (!id) {
-        id = Util.getUID(NAME)
+        id = getUID(NAME)
         config.target.id = id
       }
       config.target = `#${id}`
     }
 
-    Util.typeCheckConfig(NAME, config, DefaultType)
+    typeCheckConfig(NAME, config, DefaultType)
 
     return config
   }
@@ -284,7 +290,7 @@ class ScrollSpy {
   }
 
   _clear() {
-    Util.makeArray(SelectorEngine.find(this._selector))
+    makeArray(SelectorEngine.find(this._selector))
       .filter((node) => node.classList.contains(ClassName.ACTIVE))
       .forEach((node) => node.classList.remove(ClassName.ACTIVE))
   }
@@ -321,7 +327,7 @@ class ScrollSpy {
  */
 
 EventHandler.on(window, Event.LOAD_DATA_API, () => {
-  Util.makeArray(SelectorEngine.find(Selector.DATA_SPY))
+  makeArray(SelectorEngine.find(Selector.DATA_SPY))
     .forEach((spy) => new ScrollSpy(spy, Manipulator.getDataAttributes(spy)))
 })
 
@@ -331,7 +337,6 @@ EventHandler.on(window, Event.LOAD_DATA_API, () => {
  * ------------------------------------------------------------------------
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = ScrollSpy._jQueryInterface
index dab38c51c9c6cf79a59efbe4a187bc62d8a27781..4320ee9eab5495517ac499cf9ad103f7ad383385 100644 (file)
@@ -5,10 +5,18 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  getSelectorFromElement,
+  getTransitionDurationFromElement,
+  makeArray,
+  reflow
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -80,11 +88,11 @@ class Tab {
     let target
     let previous
     const listElement = SelectorEngine.closest(this._element, Selector.NAV_LIST_GROUP)
-    const selector = Util.getSelectorFromElement(this._element)
+    const selector = getSelectorFromElement(this._element)
 
     if (listElement) {
       const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? Selector.ACTIVE_UL : Selector.ACTIVE
-      previous = Util.makeArray(SelectorEngine.find(itemSelector, listElement))
+      previous = makeArray(SelectorEngine.find(itemSelector, listElement))
       previous = previous[previous.length - 1]
     }
 
@@ -153,11 +161,11 @@ class Tab {
     )
 
     if (active && isTransitioning) {
-      const transitionDuration = Util.getTransitionDurationFromElement(active)
+      const transitionDuration = getTransitionDurationFromElement(active)
       active.classList.remove(ClassName.SHOW)
 
-      EventHandler.one(active, Util.TRANSITION_END, complete)
-      Util.emulateTransitionEnd(active, transitionDuration)
+      EventHandler.one(active, TRANSITION_END, complete)
+      emulateTransitionEnd(active, transitionDuration)
     } else {
       complete()
     }
@@ -183,7 +191,7 @@ class Tab {
       element.setAttribute('aria-selected', true)
     }
 
-    Util.reflow(element)
+    reflow(element)
 
     if (element.classList.contains(ClassName.FADE)) {
       element.classList.add(ClassName.SHOW)
@@ -193,7 +201,7 @@ class Tab {
       const dropdownElement = SelectorEngine.closest(element, Selector.DROPDOWN)
 
       if (dropdownElement) {
-        Util.makeArray(SelectorEngine.find(Selector.DROPDOWN_TOGGLE))
+        makeArray(SelectorEngine.find(Selector.DROPDOWN_TOGGLE))
           .forEach((dropdown) => dropdown.classList.add(ClassName.ACTIVE))
       }
 
@@ -242,9 +250,9 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (
  * ------------------------------------------------------------------------
  * jQuery
  * ------------------------------------------------------------------------
+ * add .tab to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Tab._jQueryInterface
index 6e878e5c80677eec6ff51dc844aab460fadac4f4..fba559197faf7a588831d34f5318764b4c672235 100644 (file)
@@ -5,10 +5,16 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  getTransitionDurationFromElement,
+  typeCheckConfig
+} from './util/index'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -104,10 +110,10 @@ class Toast {
     this._element.classList.remove(ClassName.HIDE)
     this._element.classList.add(ClassName.SHOWING)
     if (this._config.animation) {
-      const transitionDuration = Util.getTransitionDurationFromElement(this._element)
+      const transitionDuration = getTransitionDurationFromElement(this._element)
 
-      EventHandler.one(this._element, Util.TRANSITION_END, complete)
-      Util.emulateTransitionEnd(this._element, transitionDuration)
+      EventHandler.one(this._element, TRANSITION_END, complete)
+      emulateTransitionEnd(this._element, transitionDuration)
     } else {
       complete()
     }
@@ -153,7 +159,7 @@ class Toast {
       ...typeof config === 'object' && config ? config : {}
     }
 
-    Util.typeCheckConfig(
+    typeCheckConfig(
       NAME,
       config,
       this.constructor.DefaultType
@@ -179,10 +185,10 @@ class Toast {
 
     this._element.classList.remove(ClassName.SHOW)
     if (this._config.animation) {
-      const transitionDuration = Util.getTransitionDurationFromElement(this._element)
+      const transitionDuration = getTransitionDurationFromElement(this._element)
 
-      EventHandler.one(this._element, Util.TRANSITION_END, complete)
-      Util.emulateTransitionEnd(this._element, transitionDuration)
+      EventHandler.one(this._element, TRANSITION_END, complete)
+      emulateTransitionEnd(this._element, transitionDuration)
     } else {
       complete()
     }
@@ -221,7 +227,6 @@ class Toast {
  *  add .toast to jQuery only if jQuery is present
  */
 
-const $ = Util.jQuery
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
   $.fn[NAME]               = Toast._jQueryInterface
index 8c2dbe7b2354ac098a0a798bb8a08ea7cb20a24a..13a5c62b7e4f513e0d52d2a514664fb3e7c6d74f 100644 (file)
@@ -5,16 +5,27 @@
  * --------------------------------------------------------------------------
  */
 
+import {
+  jQuery as $,
+  TRANSITION_END,
+  emulateTransitionEnd,
+  findShadowRoot,
+  getTransitionDurationFromElement,
+  getUID,
+  isElement,
+  makeArray,
+  noop,
+  typeCheckConfig
+} from './util/index'
 import {
   DefaultWhitelist,
   sanitizeHtml
-} from './tools/sanitizer'
+} from './util/sanitizer'
 import Data from './dom/data'
 import EventHandler from './dom/eventHandler'
 import Manipulator from './dom/manipulator'
 import Popper from 'popper.js'
 import SelectorEngine from './dom/selectorEngine'
-import Util from './util'
 
 /**
  * ------------------------------------------------------------------------
@@ -257,7 +268,7 @@ class Tooltip {
 
     if (this.isWithContent() && this._isEnabled) {
       const showEvent = EventHandler.trigger(this.element, this.constructor.Event.SHOW)
-      const shadowRoot = Util.findShadowRoot(this.element)
+      const shadowRoot = findShadowRoot(this.element)
       const isInTheDom = shadowRoot !== null
         ? shadowRoot.contains(this.element)
         : this.element.ownerDocument.documentElement.contains(this.element)
@@ -267,7 +278,7 @@ class Tooltip {
       }
 
       const tip   = this.getTipElement()
-      const tipId = Util.getUID(this.constructor.NAME)
+      const tipId = getUID(this.constructor.NAME)
 
       tip.setAttribute('id', tipId)
       this.element.setAttribute('aria-describedby', tipId)
@@ -323,8 +334,8 @@ class Tooltip {
       // only needed because of broken event delegation on iOS
       // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
       if ('ontouchstart' in document.documentElement) {
-        Util.makeArray(document.body.children).forEach((element) => {
-          EventHandler.on(element, 'mouseover', Util.noop())
+        makeArray(document.body.children).forEach((element) => {
+          EventHandler.on(element, 'mouseover', noop())
         })
       }
 
@@ -343,9 +354,9 @@ 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)
+        const transitionDuration = getTransitionDurationFromElement(this.tip)
+        EventHandler.one(this.tip, TRANSITION_END, complete)
+        emulateTransitionEnd(this.tip, transitionDuration)
       } else {
         complete()
       }
@@ -381,8 +392,8 @@ class Tooltip {
     // If this is a touch-enabled device we remove the extra
     // 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))
+      makeArray(document.body.children)
+        .forEach((element) => EventHandler.off(element, 'mouseover', noop))
     }
 
     this._activeTrigger[Trigger.CLICK] = false
@@ -390,9 +401,10 @@ class Tooltip {
     this._activeTrigger[Trigger.HOVER] = false
 
     if (this.tip.classList.contains(ClassName.FADE)) {
-      const transitionDuration = Util.getTransitionDurationFromElement(tip)
-      EventHandler.one(tip, Util.TRANSITION_END, complete)
-      Util.emulateTransitionEnd(tip, transitionDuration)
+      const transitionDuration = getTransitionDurationFromElement(tip)
+
+      EventHandler.one(tip, TRANSITION_END, complete)
+      emulateTransitionEnd(tip, transitionDuration)
     } else {
       complete()
     }
@@ -507,7 +519,7 @@ class Tooltip {
       return document.body
     }
 
-    if (Util.isElement(this.config.container)) {
+    if (isElement(this.config.container)) {
       return this.config.container
     }
 
@@ -705,7 +717,7 @@ class Tooltip {
       config.content = config.content.toString()
     }
 
-    Util.typeCheckConfig(
+    typeCheckConfig(
       NAME,
       config,
       this.constructor.DefaultType
@@ -795,8 +807,9 @@ class Tooltip {
  * ------------------------------------------------------------------------
  * jQuery
  * ------------------------------------------------------------------------
+ * add .tooltip to jQuery only if jQuery is present
  */
-const $ = Util.jQuery
+
 if (typeof $ !== 'undefined') {
   const JQUERY_NO_CONFLICT  = $.fn[NAME]
   $.fn[NAME]                = Tooltip._jQueryInterface
diff --git a/js/src/util.js b/js/src/util.js
deleted file mode 100644 (file)
index 8b6226f..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * --------------------------------------------------------------------------
- * Bootstrap (v4.3.1): util.js
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * --------------------------------------------------------------------------
- */
-
-/**
- * ------------------------------------------------------------------------
- * Private TransitionEnd Helpers
- * ------------------------------------------------------------------------
- */
-
-const MAX_UID = 1000000
-const MILLISECONDS_MULTIPLIER = 1000
-
-// Shoutout AngusCroll (https://goo.gl/pxwQGp)
-function toType(obj) {
-  return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase()
-}
-
-/**
- * --------------------------------------------------------------------------
- * Public Util Api
- * --------------------------------------------------------------------------
- */
-
-const Util = {
-  TRANSITION_END: 'transitionend',
-
-  getUID(prefix) {
-    do {
-      // eslint-disable-next-line no-bitwise
-      prefix += ~~(Math.random() * MAX_UID) // "~~" acts like a faster Math.floor() here
-    } while (document.getElementById(prefix))
-    return prefix
-  },
-
-  getSelectorFromElement(element) {
-    let selector = element.getAttribute('data-target')
-
-    if (!selector || selector === '#') {
-      const hrefAttr = element.getAttribute('href')
-      selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''
-    }
-
-    try {
-      return document.querySelector(selector) ? selector : null
-    } catch (err) {
-      return null
-    }
-  },
-
-  getTransitionDurationFromElement(element) {
-    if (!element) {
-      return 0
-    }
-
-    // Get transition-duration of the element
-    let transitionDuration = window.getComputedStyle(element).transitionDuration
-    let transitionDelay = window.getComputedStyle(element).transitionDelay
-
-    const floatTransitionDuration = parseFloat(transitionDuration)
-    const floatTransitionDelay = parseFloat(transitionDelay)
-
-    // Return 0 if element or transition duration is not found
-    if (!floatTransitionDuration && !floatTransitionDelay) {
-      return 0
-    }
-
-    // If multiple durations are defined, take the first
-    transitionDuration = transitionDuration.split(',')[0]
-    transitionDelay = transitionDelay.split(',')[0]
-
-    return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
-  },
-
-  reflow(element) {
-    return element.offsetHeight
-  },
-
-  triggerTransitionEnd(element) {
-    element.dispatchEvent(new Event(Util.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(() => {
-      if (!called) {
-        Util.triggerTransitionEnd(element)
-      }
-    }, emulatedDuration)
-  },
-
-  typeCheckConfig(componentName, config, configTypes) {
-    for (const property in configTypes) {
-      if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
-        const expectedTypes = configTypes[property]
-        const value         = config[property]
-        const valueType     = value && Util.isElement(value)
-          ? 'element' : toType(value)
-
-        if (!new RegExp(expectedTypes).test(valueType)) {
-          throw new Error(
-            `${componentName.toUpperCase()}: ` +
-            `Option "${property}" provided type "${valueType}" ` +
-            `but expected type "${expectedTypes}".`)
-        }
-      }
-    }
-  },
-
-  makeArray(nodeList) {
-    if (!nodeList) {
-      return []
-    }
-
-    return [].slice.call(nodeList)
-  },
-
-  isVisible(element) {
-    if (!element) {
-      return false
-    }
-
-    if (element.style !== null && element.parentNode !== null && typeof element.parentNode.style !== 'undefined') {
-      return element.style.display !== 'none' &&
-        element.parentNode.style.display !== 'none' &&
-        element.style.visibility !== 'hidden'
-    }
-    return false
-  },
-
-  findShadowRoot(element) {
-    if (!document.documentElement.attachShadow) {
-      return null
-    }
-
-    // Can find the shadow root otherwise it'll return the document
-    if (typeof element.getRootNode === 'function') {
-      const root = element.getRootNode()
-      return root instanceof ShadowRoot ? root : null
-    }
-
-    if (element instanceof ShadowRoot) {
-      return element
-    }
-
-    // when we don't find a shadow root
-    if (!element.parentNode) {
-      return null
-    }
-
-    return Util.findShadowRoot(element.parentNode)
-  },
-
-  noop() {
-    // eslint-disable-next-line no-empty-function
-    return function () {}
-  },
-
-  get jQuery() {
-    return window.jQuery
-  }
-}
-
-export default Util
diff --git a/js/src/util/index.js b/js/src/util/index.js
new file mode 100644 (file)
index 0000000..7c86a95
--- /dev/null
@@ -0,0 +1,177 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v4.3.1): util/index.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+const MAX_UID = 1000000
+const MILLISECONDS_MULTIPLIER = 1000
+const TRANSITION_END = 'transitionend'
+const jQuery = window.jQuery
+
+// Shoutout AngusCroll (https://goo.gl/pxwQGp)
+const toType = (obj) => ({}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase())
+
+/**
+ * --------------------------------------------------------------------------
+ * Public Util Api
+ * --------------------------------------------------------------------------
+ */
+
+const getUID = (prefix) => {
+  do {
+    // eslint-disable-next-line no-bitwise
+    prefix += ~~(Math.random() * MAX_UID) // "~~" acts like a faster Math.floor() here
+  } while (document.getElementById(prefix))
+  return prefix
+}
+
+const getSelectorFromElement = (element) => {
+  let selector = element.getAttribute('data-target')
+
+  if (!selector || selector === '#') {
+    const hrefAttr = element.getAttribute('href')
+
+    selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''
+  }
+
+  try {
+    return document.querySelector(selector) ? selector : null
+  } catch (err) {
+    return null
+  }
+}
+
+const getTransitionDurationFromElement = (element) => {
+  if (!element) {
+    return 0
+  }
+
+  // Get transition-duration of the element
+  let {
+    transitionDuration,
+    transitionDelay
+  } = window.getComputedStyle(element)
+
+  const floatTransitionDuration = parseFloat(transitionDuration)
+  const floatTransitionDelay = parseFloat(transitionDelay)
+
+  // Return 0 if element or transition duration is not found
+  if (!floatTransitionDuration && !floatTransitionDelay) {
+    return 0
+  }
+
+  // If multiple durations are defined, take the first
+  transitionDuration = transitionDuration.split(',')[0]
+  transitionDelay = transitionDelay.split(',')[0]
+
+  return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
+}
+
+const triggerTransitionEnd = (element) => {
+  element.dispatchEvent(new Event(TRANSITION_END))
+}
+
+const isElement = (obj) => (obj[0] || obj).nodeType
+
+const emulateTransitionEnd = (element, duration) => {
+  let called = false
+  const durationPadding = 5
+  const emulatedDuration = duration + durationPadding
+  function listener() {
+    called = true
+    element.removeEventListener(TRANSITION_END, listener)
+  }
+
+  element.addEventListener(TRANSITION_END, listener)
+  setTimeout(() => {
+    if (!called) {
+      triggerTransitionEnd(element)
+    }
+  }, emulatedDuration)
+}
+
+const typeCheckConfig = (componentName, config, configTypes) => {
+  Object.keys(configTypes)
+    .forEach((property) => {
+      const expectedTypes = configTypes[property]
+      const value         = config[property]
+      const valueType     = value && isElement(value)
+        ? 'element' : toType(value)
+
+      if (!new RegExp(expectedTypes).test(valueType)) {
+        throw new Error(
+          `${componentName.toUpperCase()}: ` +
+          `Option "${property}" provided type "${valueType}" ` +
+          `but expected type "${expectedTypes}".`)
+      }
+    })
+}
+
+const makeArray = (nodeList) => {
+  if (!nodeList) {
+    return []
+  }
+
+  return [].slice.call(nodeList)
+}
+
+const isVisible = (element) => {
+  if (!element) {
+    return false
+  }
+
+  if (element.style && element.parentNode && element.parentNode.style) {
+    return element.style.display !== 'none' &&
+      element.parentNode.style.display !== 'none' &&
+      element.style.visibility !== 'hidden'
+  }
+
+  return false
+}
+
+const findShadowRoot = (element) => {
+  if (!document.documentElement.attachShadow) {
+    return null
+  }
+
+  // Can find the shadow root otherwise it'll return the document
+  if (typeof element.getRootNode === 'function') {
+    const root = element.getRootNode()
+    return root instanceof ShadowRoot ? root : null
+  }
+
+  if (element instanceof ShadowRoot) {
+    return element
+  }
+
+  // when we don't find a shadow root
+  if (!element.parentNode) {
+    return null
+  }
+
+  return findShadowRoot(element.parentNode)
+}
+
+// eslint-disable-next-line no-empty-function
+const noop = () => function () {}
+
+const reflow = (element) => element.offsetHeight
+
+export {
+  jQuery,
+  TRANSITION_END,
+  getUID,
+  getSelectorFromElement,
+  getTransitionDurationFromElement,
+  triggerTransitionEnd,
+  isElement,
+  emulateTransitionEnd,
+  typeCheckConfig,
+  makeArray,
+  isVisible,
+  findShadowRoot,
+  noop,
+  reflow
+}
similarity index 89%
rename from js/src/tools/sanitizer.js
rename to js/src/util/sanitizer.js
index 3c1608fee8e3f631a39cc910fe144cd74de6ca11..f8bb172a95525d6012c15891ae9be7a63a4f7ed6 100644 (file)
@@ -1,11 +1,13 @@
 /**
  * --------------------------------------------------------------------------
- * Bootstrap (v4.3.1): tools/sanitizer.js
+ * Bootstrap (v4.3.1): util/sanitizer.js
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  * --------------------------------------------------------------------------
  */
 
-import Util from '../util'
+import {
+  makeArray
+} from './index'
 
 const uriAttrs = [
   'background',
@@ -20,40 +22,6 @@ const uriAttrs = [
 
 const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
 
-export const DefaultWhitelist = {
-  // Global attributes allowed on any supplied element below.
-  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
-  a: ['target', 'href', 'title', 'rel'],
-  area: [],
-  b: [],
-  br: [],
-  col: [],
-  code: [],
-  div: [],
-  em: [],
-  hr: [],
-  h1: [],
-  h2: [],
-  h3: [],
-  h4: [],
-  h5: [],
-  h6: [],
-  i: [],
-  img: ['src', 'alt', 'title', 'width', 'height'],
-  li: [],
-  ol: [],
-  p: [],
-  pre: [],
-  s: [],
-  small: [],
-  span: [],
-  sub: [],
-  sup: [],
-  strong: [],
-  u: [],
-  ul: []
-}
-
 /**
  * A pattern that recognizes a commonly useful subset of URLs that are safe.
  *
@@ -68,7 +36,7 @@ const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|
  */
 const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i
 
-function allowedAttribute(attr, allowedAttributeList) {
+const allowedAttribute = (attr, allowedAttributeList) => {
   const attrName = attr.nodeName.toLowerCase()
 
   if (allowedAttributeList.indexOf(attrName) !== -1) {
@@ -91,8 +59,42 @@ function allowedAttribute(attr, allowedAttributeList) {
   return false
 }
 
+export const DefaultWhitelist = {
+  // Global attributes allowed on any supplied element below.
+  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
+  a: ['target', 'href', 'title', 'rel'],
+  area: [],
+  b: [],
+  br: [],
+  col: [],
+  code: [],
+  div: [],
+  em: [],
+  hr: [],
+  h1: [],
+  h2: [],
+  h3: [],
+  h4: [],
+  h5: [],
+  h6: [],
+  i: [],
+  img: ['src', 'alt', 'title', 'width', 'height'],
+  li: [],
+  ol: [],
+  p: [],
+  pre: [],
+  s: [],
+  small: [],
+  span: [],
+  sub: [],
+  sup: [],
+  strong: [],
+  u: [],
+  ul: []
+}
+
 export function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
-  if (unsafeHtml.length === 0) {
+  if (!unsafeHtml.length) {
     return unsafeHtml
   }
 
@@ -103,19 +105,19 @@ export function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
   const domParser = new window.DOMParser()
   const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
   const whitelistKeys = Object.keys(whiteList)
-  const elements = Util.makeArray(createdDocument.body.querySelectorAll('*'))
+  const elements = makeArray(createdDocument.body.querySelectorAll('*'))
 
   for (let i = 0, len = elements.length; i < len; i++) {
     const el = elements[i]
     const elName = el.nodeName.toLowerCase()
 
-    if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) {
+    if (whitelistKeys.indexOf(elName) === -1) {
       el.parentNode.removeChild(el)
 
       continue
     }
 
-    const attributeList = Util.makeArray(el.attributes)
+    const attributeList = makeArray(el.attributes)
     const whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])
 
     attributeList.forEach((attr) => {
diff --git a/js/tests/index.html b/js/tests/index.html
deleted file mode 100644 (file)
index 77c29f8..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-    <title>Bootstrap Plugin Test Suite</title>
-
-    <!-- jQuery -->
-    <script src="../../node_modules/jquery/dist/jquery.slim.min.js"></script>
-    <script src="../../node_modules/popper.js/dist/umd/popper.min.js"></script>
-
-    <!-- QUnit -->
-    <link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css" media="screen">
-    <script src="../../node_modules/qunit/qunit/qunit.js"></script>
-
-    <!-- Sinon -->
-    <script src="../../node_modules/sinon/pkg/sinon-no-sourcemaps.js"></script>
-
-    <!-- Hammer simulator -->
-    <script src="../../node_modules/hammer-simulator/index.js"></script>
-
-    <script>
-      // Disable jQuery event aliases to ensure we don't accidentally use any of them
-      [
-        'blur',
-        'focus',
-        'focusin',
-        'focusout',
-        'resize',
-        'scroll',
-        'click',
-        'dblclick',
-        'mousedown',
-        'mouseup',
-        'mousemove',
-        'mouseover',
-        'mouseout',
-        'mouseenter',
-        'mouseleave',
-        'change',
-        'select',
-        'submit',
-        'keydown',
-        'keypress',
-        'keyup',
-        'contextmenu'
-      ].forEach(function(eventAlias) {
-        $.fn[eventAlias] = function() {
-          throw new Error('Using the ".' + eventAlias + '()" method is not allowed, so that Bootstrap can be compatible with custom jQuery builds which exclude the "event aliases" module that defines said method. See https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#js')
-        }
-      })
-
-      // Require assert.expect in each test
-      QUnit.config.requireExpects = true
-
-      // See https://github.com/axemclion/grunt-saucelabs#test-result-details-with-qunit
-      var log = []
-      var testName
-
-      QUnit.done(function(testResults) {
-        var tests = []
-        for (var i = 0; i < log.length; i++) {
-          var details = log[i]
-          tests.push({
-            name: details.name,
-            result: details.result,
-            expected: details.expected,
-            actual: details.actual,
-            source: details.source
-          })
-        }
-        testResults.tests = tests
-
-        window.global_test_results = testResults
-      })
-
-      QUnit.testStart(function(testDetails) {
-        QUnit.log(function(details) {
-          if (!details.result) {
-            details.name = testDetails.name
-            log.push(details)
-          }
-        })
-      })
-
-      // Display fixture on-screen on iOS to avoid false positives
-      // See https://github.com/twbs/bootstrap/pull/15955
-      if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
-        QUnit.begin(function() {
-          $('#qunit-fixture').css({ top: 0, left: 0 })
-        })
-
-        QUnit.done(function() {
-          $('#qunit-fixture').css({ top: '', left: '' })
-        })
-      }
-    </script>
-
-    <!-- Transpiled Plugins -->
-    <script src="../dist/util.js"></script>
-    <script src="../dist/dom/polyfill.js"></script>
-    <script src="../dist/dom/manipulator.js"></script>
-    <script src="../dist/dom/eventHandler.js"></script>
-    <script src="../dist/dom/selectorEngine.js"></script>
-    <script src="../dist/dom/data.js"></script>
-    <script src="../dist/alert.js"></script>
-    <script src="../dist/button.js"></script>
-    <script src="../dist/carousel.js"></script>
-    <script src="../dist/collapse.js"></script>
-    <script src="../dist/dropdown.js"></script>
-    <script src="../dist/modal.js"></script>
-    <script src="../dist/scrollspy.js"></script>
-    <script src="../dist/tab.js"></script>
-    <script src="../dist/tooltip.js"></script>
-    <script src="../dist/popover.js"></script>
-    <script src="../dist/toast.js"></script>
-
-    <!-- Unit Tests -->
-    <script src="unit/dom/eventHandler.js"></script>
-    <script src="unit/dom/manipulator.js"></script>
-    <script src="unit/dom/data.js"></script>
-    <script src="unit/dom/selectorEngine.js"></script>
-    <script src="unit/alert.js"></script>
-    <script src="unit/button.js"></script>
-    <script src="unit/carousel.js"></script>
-    <script src="unit/collapse.js"></script>
-    <script src="unit/dropdown.js"></script>
-    <script src="unit/modal.js"></script>
-    <script src="unit/scrollspy.js"></script>
-    <script src="unit/tab.js"></script>
-    <script src="unit/tooltip.js"></script>
-    <script src="unit/popover.js"></script>
-    <script src="unit/util.js"></script>
-    <script src="unit/toast.js"></script>
-  </head>
-  <body>
-    <div id="qunit-container">
-      <div id="qunit"></div>
-      <div id="qunit-fixture"></div>
-    </div>
-  </body>
-</html>
index 23caeab73200a729c5e6cd027966c32419cb0367..f2d3b0d7bec1acf9f9b6e2b02c9e0d3528b8ba92 100644 (file)
@@ -2,8 +2,6 @@ import 'popper.js'
 import bootstrap from '../../../dist/js/bootstrap'
 
 window.addEventListener('load', () => {
-  document.getElementById('resultUID').innerHTML = bootstrap.Util.getUID('bs')
-
-  bootstrap.Util.makeArray(document.querySelectorAll('[data-toggle="tooltip"]'))
+  Array.from(document.querySelectorAll('[data-toggle="tooltip"]'))
     .map((tooltipNode) => new bootstrap.Tooltip(tooltipNode))
 })
index e5b33a84dcd474a2796b328f3b65e96981b78ec6..f14330f47ef174fedff7cf32a4c9ca49355b18a2 100644 (file)
   <body>
     <div class="container">
       <h1>Hello, world!</h1>
-      <div class="col-12">
-        <div class="mt-5 mb-3">
-          <span>Util.getUID: </span>
-          <span id="resultUID"></span>
-        </div>
+      <div class="col-12 mt-5">
         <button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top">
           Tooltip on top
         </button>
index 641ac88949cd083ebcbfb85dff6ce657848d030b..704d3c541d22088176bd063e10e155c43e29f0a7 100644 (file)
@@ -11,6 +11,7 @@ const {
 const jqueryFile = process.env.USE_OLD_JQUERY ? 'https://code.jquery.com/jquery-1.9.1.min.js' : 'node_modules/jquery/dist/jquery.slim.min.js'
 const bundle = process.env.BUNDLE === 'true'
 const browserStack = process.env.BROWSER === 'true'
+const debug = process.env.DEBUG === 'true'
 
 const frameworks = [
   'qunit',
@@ -28,11 +29,11 @@ const detectBrowsers = {
   usePhantomJS: false,
   postDetection(availableBrowser) {
     if (typeof process.env.TRAVIS_JOB_ID !== 'undefined' || availableBrowser.includes('Chrome')) {
-      return ['ChromeHeadless']
+      return debug ? ['Chrome'] : ['ChromeHeadless']
     }
 
     if (availableBrowser.includes('Firefox')) {
-      return ['FirefoxHeadless']
+      return debug ? ['Firefox'] : ['FirefoxHeadless']
     }
 
     throw new Error('Please install Firefox or Chrome')
@@ -76,7 +77,8 @@ if (bundle) {
   conf.detectBrowsers = detectBrowsers
   files = files.concat([
     jqueryFile,
-    'dist/js/bootstrap.js'
+    'dist/js/bootstrap.js',
+    'js/tests/unit/*.js'
   ])
 } else if (browserStack) {
   conf.hostname = ip.address()
@@ -93,7 +95,8 @@ if (bundle) {
   reporters.push('BrowserStack')
   files = files.concat([
     'node_modules/jquery/dist/jquery.slim.min.js',
-    'js/coverage/dist/util.js',
+    'js/coverage/dist/util/util.js',
+    'js/coverage/dist/util/sanitizer.js',
     'js/coverage/dist/dom/polyfill.js',
     'js/coverage/dist/dom/eventHandler.js',
     'js/coverage/dist/dom/selectorEngine.js',
@@ -103,7 +106,8 @@ if (bundle) {
     'js/coverage/dist/tooltip.js',
     'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
     'js/tests/unit/*.js',
-    'js/tests/unit/dom/*.js'
+    'js/tests/unit/dom/*.js',
+    'js/tests/unit/util/*.js'
   ])
 } else {
   frameworks.push('detectBrowsers')
@@ -115,7 +119,8 @@ if (bundle) {
   )
   files = files.concat([
     jqueryFile,
-    'js/coverage/dist/util.js',
+    'js/coverage/dist/util/util.js',
+    'js/coverage/dist/util/sanitizer.js',
     'js/coverage/dist/dom/polyfill.js',
     'js/coverage/dist/dom/eventHandler.js',
     'js/coverage/dist/dom/selectorEngine.js',
@@ -125,7 +130,8 @@ if (bundle) {
     'js/coverage/dist/tooltip.js',
     'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
     'js/tests/unit/*.js',
-    'js/tests/unit/dom/*.js'
+    'js/tests/unit/dom/*.js',
+    'js/tests/unit/util/*.js'
   ])
   reporters.push('coverage-istanbul')
   conf.customLaunchers = customLaunchers
@@ -153,9 +159,12 @@ if (bundle) {
       }
     }
   }
-}
 
-files.push('js/tests/unit/*.js')
+  if (debug) {
+    conf.singleRun = false
+    conf.autoWatch = true
+  }
+}
 
 conf.frameworks = frameworks
 conf.plugins = plugins
index dfcf1eaa50034e31c3aa05a7c4ad13f9310aede3..19ab5d998619dc7fde9bec9a94cdd0042654844c 100644 (file)
@@ -8,6 +8,7 @@
     "bootstrap": false,
     "sinon": false,
     "Util": false,
+    "Sanitizer": false,
     "Data": false,
     "Alert": false,
     "Button": false,
index a9a3df838be02680471967eb617e0c97c8750d9c..6939c5e5bfb30b689aa0cc03137fb57fca852205 100644 (file)
@@ -695,13 +695,10 @@ $(function () {
     ].join('')
 
     var $modal = $(modalHTML).appendTo('#qunit-fixture')
-    var expectedTransitionDuration = 300
-    var spy = sinon.spy(Util, 'getTransitionDurationFromElement')
 
     $modal.on('shown.bs.modal', function () {
-      assert.ok(spy.returned(expectedTransitionDuration))
       $style.remove()
-      spy.restore()
+      assert.ok(true)
       done()
     })
       .bootstrapModal('show')
index 85fafe4b8c8c5455a5b418c09886e3c10be561d2..b542cbfb19afa287d5d5dc498c1f6bbcf4a55476 100644 (file)
@@ -722,8 +722,10 @@ $(function () {
 
   QUnit.test('should not reload the tooltip on subsequent mouseenter events', function (assert) {
     assert.expect(1)
+    var fakeId = 1
     var titleHtml = function () {
-      var uid = Util.getUID('tooltip')
+      var uid = fakeId
+      fakeId++
       return '<p id="tt-content">' + uid + '</p><p>' + uid + '</p><p>' + uid + '</p>'
     }
 
@@ -753,8 +755,10 @@ $(function () {
   QUnit.test('should not reload the tooltip if the mouse leaves and re-enters before hiding', function (assert) {
     assert.expect(4)
 
+    var fakeId = 1
     var titleHtml = function () {
-      var uid = Util.getUID('tooltip')
+      var uid = 'tooltip' + fakeId
+      fakeId++
       return '<p id="tt-content">' + uid + '</p><p>' + uid + '</p><p>' + uid + '</p>'
     }
 
@@ -1152,24 +1156,6 @@ $(function () {
     assert.strictEqual(tooltip.config.template.indexOf('onError'), -1)
   })
 
-  QUnit.test('should sanitize template by removing tags with XSS', function (assert) {
-    assert.expect(1)
-
-    var $trigger = $('<a href="#" rel="tooltip" data-trigger="click" title="Another tooltip"/>')
-      .appendTo('#qunit-fixture')
-      .bootstrapTooltip({
-        template: [
-          '<div>',
-          '  <a href="javascript:alert(7)">Click me</a>',
-          '  <span>Some content</span>',
-          '</div>'
-        ].join('')
-      })
-
-    var tooltip = Tooltip._getInstance($trigger[0])
-    assert.strictEqual(tooltip.config.template.indexOf('script'), -1)
-  })
-
   QUnit.test('should allow custom sanitization rules', function (assert) {
     assert.expect(2)
 
similarity index 98%
rename from js/tests/unit/util.js
rename to js/tests/unit/util/index.js
index db1412a3ba18e732ca0affa604e250d1edda3ecf..2d52ca59a0f5d474f0671ddfe9fb852e002d6403 100644 (file)
@@ -1,8 +1,6 @@
 $(function () {
   'use strict'
 
-  window.Util = typeof bootstrap !== 'undefined' ? bootstrap.Util : Util
-
   QUnit.module('util', {
     afterEach: function () {
       $('#qunit-fixture').html('')
diff --git a/js/tests/unit/util/sanitizer.js b/js/tests/unit/util/sanitizer.js
new file mode 100644 (file)
index 0000000..4120f07
--- /dev/null
@@ -0,0 +1,51 @@
+$(function () {
+  'use strict'
+
+  QUnit.module('sanitizer', {
+    afterEach: function () {
+      $('#qunit-fixture').html('')
+    }
+  })
+
+  QUnit.test('should export a default white list', function (assert) {
+    assert.expect(1)
+
+    assert.ok(Sanitizer.DefaultWhitelist)
+  })
+
+  QUnit.test('should sanitize template by removing tags with XSS', function (assert) {
+    assert.expect(1)
+
+    var template = [
+      '<div>',
+      '  <a href="javascript:alert(7)">Click me</a>',
+      '  <span>Some content</span>',
+      '</div>'
+    ].join('')
+
+    var result = Sanitizer.sanitizeHtml(template, Sanitizer.DefaultWhitelist, null)
+
+    assert.strictEqual(result.indexOf('script'), -1)
+  })
+
+  QUnit.test('should not use native api to sanitize if a custom function passed', function (assert) {
+    assert.expect(2)
+
+    var template = [
+      '<div>',
+      '  <span>Some content</span>',
+      '</div>'
+    ].join('')
+
+    function mySanitize(htmlUnsafe) {
+      return htmlUnsafe
+    }
+
+    var spy = sinon.spy(DOMParser.prototype, 'parseFromString')
+    var result = Sanitizer.sanitizeHtml(template, Sanitizer.DefaultWhitelist, mySanitize)
+
+    assert.strictEqual(result, template)
+    assert.strictEqual(spy.called, false)
+    spy.restore()
+  })
+})
index a527eab13da451e632850da79067ea66ff8bd8a2..da5236e3e50985778f7bca6363e56534d16ec1be 100644 (file)
     <script src="../../dist/toast.js"></script>
     <script>
       window.addEventListener('load', function () {
-        Util.makeArray(document.querySelectorAll('.toast'))
+        Array.from(document.querySelectorAll('.toast'))
           .forEach(function (toastNode) {
             new Toast(toastNode)
           })
 
           document.getElementById('btnShowToast').addEventListener('click', function () {
-            Util.makeArray(document.querySelectorAll('.toast'))
+            Array.from(document.querySelectorAll('.toast'))
               .forEach(function (toastNode) {
                 var toast = Toast._getInstance(toastNode)
                 toast.show()
@@ -74,7 +74,7 @@
           })
 
           document.getElementById('btnHideToast').addEventListener('click', function () {
-            Util.makeArray(document.querySelectorAll('.toast'))
+            Array.from(document.querySelectorAll('.toast'))
               .forEach(function (toastNode) {
                 var toast = Toast._getInstance(toastNode)
                 toast.hide()
index d2e023b904c39c542d5068ec80d0670a4df22550..38a397a5d43f7dbe9ae6bab090fc66a384e8037f 100644 (file)
@@ -56,6 +56,7 @@
     "js-minify-bundle": "terser --compress --mangle --comments \"/^!/\" --source-map \"content=dist/js/bootstrap.bundle.js.map,includeSources,url=bootstrap.bundle.min.js.map\" --output dist/js/bootstrap.bundle.min.js dist/js/bootstrap.bundle.js",
     "js-minify-docs": "cross-env-shell terser --mangle --comments \\\"/^!/\\\" --output site/docs/$npm_package_version_short/assets/js/docs.min.js site/docs/$npm_package_version_short/assets/js/vendor/anchor.min.js site/docs/$npm_package_version_short/assets/js/vendor/clipboard.min.js site/docs/$npm_package_version_short/assets/js/vendor/bs-custom-file-input.min.js \"site/docs/$npm_package_version_short/assets/js/src/*.js\"",
     "js-test": "npm-run-all js-test-karma* js-test-integration",
+    "js-debug": "cross-env DEBUG=true karma start js/tests/karma.conf.js",
     "js-test-karma": "karma start js/tests/karma.conf.js",
     "js-test-karma-old": "cross-env USE_OLD_JQUERY=true npm run js-test-karma",
     "js-test-karma-bundle": "cross-env BUNDLE=true npm run js-test-karma",
index 24b633bec126c3bbf5e8d4322907e87b84d3e317..01f4f55db615dedf459d0c4492e8ba0542b60639 100644 (file)
 (function () {
   'use strict'
 
+  function makeArray(list) {
+    return [].slice.call(list)
+  }
+
   // Tooltip and popover demos
-  bootstrap.Util.makeArray(document.querySelectorAll('.tooltip-demo'))
+  makeArray(document.querySelectorAll('.tooltip-demo'))
     .forEach(function (tooltip) {
       new bootstrap.Tooltip(tooltip, {
         selector: '[data-toggle="tooltip"]'
       })
     })
 
-  bootstrap.Util.makeArray(document.querySelectorAll('[data-toggle="popover"]'))
+  makeArray(document.querySelectorAll('[data-toggle="popover"]'))
     .forEach(function (popover) {
       new bootstrap.Popover(popover)
     })
 
-  bootstrap.Util.makeArray(document.querySelectorAll('.toast'))
+  makeArray(document.querySelectorAll('.toast'))
     .forEach(function (toastNode) {
       var toast = new bootstrap.Toast(toastNode, {
         autohide: false
     })
 
   // Demos within modals
-  bootstrap.Util.makeArray(document.querySelectorAll('.tooltip-test'))
+  makeArray(document.querySelectorAll('.tooltip-test'))
     .forEach(function (tooltip) {
       new bootstrap.Tooltip(tooltip)
     })
 
-  bootstrap.Util.makeArray(document.querySelectorAll('.popover-test'))
+  makeArray(document.querySelectorAll('.popover-test'))
     .forEach(function (popover) {
       new bootstrap.Popover(popover)
     })
 
   // Indeterminate checkbox example
-  bootstrap.Util.makeArray(document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]'))
+  makeArray(document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]'))
     .forEach(function (checkbox) {
       checkbox.indeterminate = true
     })
 
   // Disable empty links in docs examples
-  bootstrap.Util.makeArray(document.querySelectorAll('.bd-content [href="#"]'))
+  makeArray(document.querySelectorAll('.bd-content [href="#"]'))
     .forEach(function (link) {
       link.addEventListener('click', function (e) {
         e.preventDefault()
@@ -79,7 +83,7 @@
   }
 
   // Activate animated progress bar
-  bootstrap.Util.makeArray(document.querySelectorAll('.bd-toggle-animated-progress > .progress-bar-striped'))
+  makeArray(document.querySelectorAll('.bd-toggle-animated-progress > .progress-bar-striped'))
     .forEach(function (progressBar) {
       progressBar.addEventListener('click', function () {
         if (progressBar.classList.contains('progress-bar-animated')) {
 
   // Insert copy to clipboard button before .highlight
   var btnHtml = '<div class="bd-clipboard"><button type="button" class="btn-clipboard" title="Copy to clipboard">Copy</button></div>'
-  bootstrap.Util.makeArray(document.querySelectorAll('figure.highlight, div.highlight'))
+  makeArray(document.querySelectorAll('figure.highlight, div.highlight'))
     .forEach(function (element) {
       element.insertAdjacentHTML('beforebegin', btnHtml)
     })
 
-  bootstrap.Util.makeArray(document.querySelectorAll('.btn-clipboard'))
+  makeArray(document.querySelectorAll('.btn-clipboard'))
     .forEach(function (btn) {
       var tooltipBtn = new bootstrap.Tooltip(btn)
 
   anchors.add('.bd-content > h2, .bd-content > h3, .bd-content > h4, .bd-content > h5')
 
   // Wrap inner
-  bootstrap.Util.makeArray(document.querySelectorAll('.bd-content > h2, .bd-content > h3, .bd-content > h4, .bd-content > h5'))
+  makeArray(document.querySelectorAll('.bd-content > h2, .bd-content > h3, .bd-content > h4, .bd-content > h5'))
     .forEach(function (hEl) {
       hEl.innerHTML = '<span class="bd-content-title">' + hEl.innerHTML + '</span>'
     })
index 9dd61aa55d0f6a972882a0a7eab403ed38b1c42c..5659f6f936afd8e2bfaa77bd78bb80cb00befdb7 100644 (file)
@@ -134,12 +134,6 @@ Bootstrap's plugins don't fall back particularly gracefully when JavaScript is d
 {% endcapture %}
 {% include callout.html content=callout type="warning" %}
 
-## Util
-
-All Bootstrap's JavaScript files depend on `util.js` and it has to be included alongside the other JavaScript files. If you're using the compiled (or minified) `bootstrap.js`, there is no need to include this—it's already there.
-
-`util.js` includes utility functions and a basic helper for `transitionEnd` events as well as a CSS transition emulator. It's used by the other plugins to check for CSS transition support and to catch hanging transitions.
-
 ## Sanitizer
 
 Tooltips and Popovers use our built-in sanitizer to sanitize options which accept HTML.
index 299cdf9b16a9d316b147fd83beeaa3c881f35ea5..b8c5f8a0daf4adb34f1df7f7d5f9622ced4e8bb5 100644 (file)
@@ -21,7 +21,6 @@ import 'bootstrap';
 Alternatively, you may **import plugins individually** as needed:
 
 {% highlight js %}
-import 'bootstrap/js/dist/util';
 import 'bootstrap/js/dist/alert';
 ...
 {% endhighlight %}