diff options
| author | XhmikosR <[email protected]> | 2021-08-18 07:29:56 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-08-18 07:29:56 +0300 |
| commit | 433a148c9e61aa942801fd8101dfa5c4045fdaed (patch) | |
| tree | f41db59fd06019169df5ea0338213ec0e298f226 /js/src/util/index.js | |
| parent | b97cfa163b5098db70e03b27c91fca5dde9c267e (diff) | |
| parent | 18b3e1ac71f73d006756684a285c5a818e2d1454 (diff) | |
| download | bootstrap-global-focus-vars.tar.xz bootstrap-global-focus-vars.zip | |
Merge branch 'main' into global-focus-varsglobal-focus-vars
Diffstat (limited to 'js/src/util/index.js')
| -rw-r--r-- | js/src/util/index.js | 163 |
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 } |
