aboutsummaryrefslogtreecommitdiff
path: root/js/src/util/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/util/index.js')
-rw-r--r--js/src/util/index.js163
1 files changed, 130 insertions, 33 deletions
diff --git a/js/src/util/index.js b/js/src/util/index.js
index ae3cd2ac0..bed2534e5 100644
--- a/js/src/util/index.js
+++ b/js/src/util/index.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.0.0-beta2): util/index.js
+ * Bootstrap (v5.1.0): util/index.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
@@ -48,7 +48,7 @@ const getSelector = element => {
// Just in case some CMS puts out a full URL with the anchor appended
if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {
- hrefAttr = '#' + hrefAttr.split('#')[1]
+ hrefAttr = `#${hrefAttr.split('#')[1]}`
}
selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null
@@ -100,24 +100,28 @@ const triggerTransitionEnd = element => {
element.dispatchEvent(new Event(TRANSITION_END))
}
-const isElement = obj => (obj[0] || obj).nodeType
+const isElement = obj => {
+ if (!obj || typeof obj !== 'object') {
+ return false
+ }
-const emulateTransitionEnd = (element, duration) => {
- let called = false
- const durationPadding = 5
- const emulatedDuration = duration + durationPadding
+ if (typeof obj.jquery !== 'undefined') {
+ obj = obj[0]
+ }
- function listener() {
- called = true
- element.removeEventListener(TRANSITION_END, listener)
+ return typeof obj.nodeType !== 'undefined'
+}
+
+const getElement = obj => {
+ if (isElement(obj)) { // it's a jQuery object or a node element
+ return obj.jquery ? obj[0] : obj
}
- element.addEventListener(TRANSITION_END, listener)
- setTimeout(() => {
- if (!called) {
- triggerTransitionEnd(element)
- }
- }, emulatedDuration)
+ if (typeof obj === 'string' && obj.length > 0) {
+ return document.querySelector(obj)
+ }
+
+ return null
}
const typeCheckConfig = (componentName, config, configTypes) => {
@@ -128,29 +132,34 @@ const typeCheckConfig = (componentName, config, configTypes) => {
if (!new RegExp(expectedTypes).test(valueType)) {
throw new TypeError(
- `${componentName.toUpperCase()}: ` +
- `Option "${property}" provided type "${valueType}" ` +
- `but expected type "${expectedTypes}".`
+ `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`
)
}
})
}
const isVisible = element => {
- if (!element) {
+ if (!isElement(element) || element.getClientRects().length === 0) {
return false
}
- if (element.style && element.parentNode && element.parentNode.style) {
- const elementStyle = getComputedStyle(element)
- const parentNodeStyle = getComputedStyle(element.parentNode)
+ return getComputedStyle(element).getPropertyValue('visibility') === 'visible'
+}
+
+const isDisabled = element => {
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) {
+ return true
+ }
+
+ if (element.classList.contains('disabled')) {
+ return true
+ }
- return elementStyle.display !== 'none' &&
- parentNodeStyle.display !== 'none' &&
- elementStyle.visibility !== 'hidden'
+ if (typeof element.disabled !== 'undefined') {
+ return element.disabled
}
- return false
+ return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'
}
const findShadowRoot = element => {
@@ -176,9 +185,20 @@ const findShadowRoot = element => {
return findShadowRoot(element.parentNode)
}
-const noop = () => function () {}
+const noop = () => {}
-const reflow = element => element.offsetHeight
+/**
+ * Trick to restart an element's animation
+ *
+ * @param {HTMLElement} element
+ * @return void
+ *
+ * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
+ */
+const reflow = element => {
+ // eslint-disable-next-line no-unused-expressions
+ element.offsetHeight
+}
const getjQuery = () => {
const { jQuery } = window
@@ -190,9 +210,18 @@ const getjQuery = () => {
return null
}
+const DOMContentLoadedCallbacks = []
+
const onDOMContentLoaded = callback => {
if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', callback)
+ // add listener on the first call when the document is in loading state
+ if (!DOMContentLoadedCallbacks.length) {
+ document.addEventListener('DOMContentLoaded', () => {
+ DOMContentLoadedCallbacks.forEach(callback => callback())
+ })
+ }
+
+ DOMContentLoadedCallbacks.push(callback)
} else {
callback()
}
@@ -200,11 +229,12 @@ const onDOMContentLoaded = callback => {
const isRTL = () => document.documentElement.dir === 'rtl'
-const defineJQueryPlugin = (name, plugin) => {
+const defineJQueryPlugin = plugin => {
onDOMContentLoaded(() => {
const $ = getjQuery()
/* istanbul ignore if */
if ($) {
+ const name = plugin.NAME
const JQUERY_NO_CONFLICT = $.fn[name]
$.fn[name] = plugin.jQueryInterface
$.fn[name].Constructor = plugin
@@ -216,21 +246,88 @@ const defineJQueryPlugin = (name, plugin) => {
})
}
+const execute = callback => {
+ if (typeof callback === 'function') {
+ callback()
+ }
+}
+
+const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
+ if (!waitForTransition) {
+ execute(callback)
+ return
+ }
+
+ const durationPadding = 5
+ const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding
+
+ let called = false
+
+ const handler = ({ target }) => {
+ if (target !== transitionElement) {
+ return
+ }
+
+ called = true
+ transitionElement.removeEventListener(TRANSITION_END, handler)
+ execute(callback)
+ }
+
+ transitionElement.addEventListener(TRANSITION_END, handler)
+ setTimeout(() => {
+ if (!called) {
+ triggerTransitionEnd(transitionElement)
+ }
+ }, emulatedDuration)
+}
+
+/**
+ * Return the previous/next element of a list.
+ *
+ * @param {array} list The list of elements
+ * @param activeElement The active element
+ * @param shouldGetNext Choose to get next or previous element
+ * @param isCycleAllowed
+ * @return {Element|elem} The proper element
+ */
+const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
+ let index = list.indexOf(activeElement)
+
+ // if the element does not exist in the list return an element depending on the direction and if cycle is allowed
+ if (index === -1) {
+ return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]
+ }
+
+ const listLength = list.length
+
+ index += shouldGetNext ? 1 : -1
+
+ if (isCycleAllowed) {
+ index = (index + listLength) % listLength
+ }
+
+ return list[Math.max(0, Math.min(index, listLength - 1))]
+}
+
export {
+ getElement,
getUID,
getSelectorFromElement,
getElementFromSelector,
getTransitionDurationFromElement,
triggerTransitionEnd,
isElement,
- emulateTransitionEnd,
typeCheckConfig,
isVisible,
+ isDisabled,
findShadowRoot,
noop,
+ getNextActiveElement,
reflow,
getjQuery,
onDOMContentLoaded,
isRTL,
- defineJQueryPlugin
+ defineJQueryPlugin,
+ execute,
+ executeAfterTransition
}