bootstrap/js/src/util/index.js

224 lines
5.4 KiB
JavaScript
Raw Normal View History

/**
* --------------------------------------------------------------------------
2020-11-24 22:37:20 +08:00
* Bootstrap (v5.0.0-beta1): util/index.js
2020-06-17 02:41:47 +08:00
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
const MAX_UID = 1000000
const MILLISECONDS_MULTIPLIER = 1000
const TRANSITION_END = 'transitionend'
// Shoutout AngusCroll (https://goo.gl/pxwQGp)
const toType = obj => {
if (obj === null || obj === undefined) {
return `${obj}`
}
return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase()
}
/**
* --------------------------------------------------------------------------
* Public Util Api
* --------------------------------------------------------------------------
*/
2019-02-26 19:20:34 +08:00
const getUID = prefix => {
do {
prefix += Math.floor(Math.random() * MAX_UID)
} while (document.getElementById(prefix))
2019-02-26 19:20:34 +08:00
return prefix
}
const getSelector = element => {
let selector = element.getAttribute('data-bs-target')
if (!selector || selector === '#') {
const hrefAttr = element.getAttribute('href')
selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null
}
return selector
}
const getSelectorFromElement = element => {
const selector = getSelector(element)
if (selector) {
return document.querySelector(selector) ? selector : null
}
return null
}
const getElementFromSelector = element => {
const selector = getSelector(element)
return selector ? document.querySelector(selector) : null
}
2019-02-26 19:20:34 +08:00
const getTransitionDurationFromElement = element => {
if (!element) {
return 0
}
// Get transition-duration of the element
2020-12-02 12:45:15 +08:00
let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
2020-05-02 21:49:33 +08:00
const floatTransitionDuration = Number.parseFloat(transitionDuration)
const floatTransitionDelay = Number.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]
2020-05-02 21:49:33 +08:00
return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
}
2019-02-26 19:20:34 +08:00
const triggerTransitionEnd = element => {
2020-03-09 21:34:07 +08:00
element.dispatchEvent(new Event(TRANSITION_END))
}
2019-02-26 19:20:34 +08:00
const isElement = obj => (obj[0] || obj).nodeType
const emulateTransitionEnd = (element, duration) => {
let called = false
const durationPadding = 5
const emulatedDuration = duration + durationPadding
2020-12-02 12:45:15 +08:00
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) => {
2020-06-10 23:40:52 +08:00
Object.keys(configTypes).forEach(property => {
const expectedTypes = configTypes[property]
const value = config[property]
const valueType = value && isElement(value) ? 'element' : toType(value)
2020-06-10 23:40:52 +08:00
if (!new RegExp(expectedTypes).test(valueType)) {
throw new TypeError(
2020-06-10 23:40:52 +08:00
`${componentName.toUpperCase()}: ` +
`Option "${property}" provided type "${valueType}" ` +
`but expected type "${expectedTypes}".`
)
2020-06-10 23:40:52 +08:00
}
})
}
2019-02-26 19:20:34 +08:00
const isVisible = element => {
if (!element) {
return false
}
if (element.style && element.parentNode && element.parentNode.style) {
const elementStyle = getComputedStyle(element)
const parentNodeStyle = getComputedStyle(element.parentNode)
return elementStyle.display !== 'none' &&
parentNodeStyle.display !== 'none' &&
elementStyle.visibility !== 'hidden'
}
return false
}
2019-02-26 19:20:34 +08:00
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)
}
const noop = () => function () {}
2019-02-26 19:20:34 +08:00
const reflow = element => element.offsetHeight
2019-08-02 21:51:05 +08:00
const getjQuery = () => {
const { jQuery } = window
if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
2019-08-02 21:51:05 +08:00
return jQuery
}
return null
}
const onDOMContentLoaded = callback => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback)
} else {
callback()
}
}
const isRTL = document.documentElement.dir === 'rtl'
const defineJQueryPlugin = (name, plugin) => {
onDOMContentLoaded(() => {
const $ = getjQuery()
/* istanbul ignore if */
if ($) {
const JQUERY_NO_CONFLICT = $.fn[name]
$.fn[name] = plugin.jQueryInterface
$.fn[name].Constructor = plugin
$.fn[name].noConflict = () => {
$.fn[name] = JQUERY_NO_CONFLICT
return plugin.jQueryInterface
}
}
})
}
export {
getUID,
getSelectorFromElement,
getElementFromSelector,
getTransitionDurationFromElement,
triggerTransitionEnd,
isElement,
emulateTransitionEnd,
typeCheckConfig,
isVisible,
findShadowRoot,
noop,
reflow,
getjQuery,
onDOMContentLoaded,
isRTL,
defineJQueryPlugin
}