diff options
| author | Bobby <[email protected]> | 2024-08-16 20:47:33 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-08-16 20:47:33 -0400 |
| commit | 6b28433d9cfde435be8ec2bd6cf91e6324d08865 (patch) | |
| tree | 8343c27b8b95ff5639233e81cf157f92e5688466 /js/src/util/index.js | |
| parent | d53094ec16ba385faae2973ddee648698b32ab24 (diff) | |
| parent | 048f56f51460df75e92a2f7b472e1c56baeb68f7 (diff) | |
| download | bootstrap-6b28433d9cfde435be8ec2bd6cf91e6324d08865.tar.xz bootstrap-6b28433d9cfde435be8ec2bd6cf91e6324d08865.zip | |
Diffstat (limited to 'js/src/util/index.js')
| -rw-r--r-- | js/src/util/index.js | 149 |
1 files changed, 61 insertions, 88 deletions
diff --git a/js/src/util/index.js b/js/src/util/index.js index 0ba6ce6f8..c271cc536 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.1.3): util/index.js + * Bootstrap util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -9,13 +9,27 @@ const MAX_UID = 1_000_000 const MILLISECONDS_MULTIPLIER = 1000 const TRANSITION_END = 'transitionend' -// Shoutout AngusCroll (https://goo.gl/pxwQGp) -const toType = obj => { - if (obj === null || obj === undefined) { - return `${obj}` +/** + * Properly escape IDs selectors to handle weird IDs + * @param {string} selector + * @returns {string} + */ +const parseSelector = selector => { + if (selector && window.CSS && window.CSS.escape) { + // document.querySelector needs escaping to handle IDs (html5+) containing for instance / + selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`) + } + + return selector +} + +// Shout-out Angus Croll (https://goo.gl/pxwQGp) +const toType = object => { + if (object === null || object === undefined) { + return `${object}` } - return Object.prototype.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase() + return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase() } /** @@ -30,47 +44,6 @@ const getUID = prefix => { return prefix } -const getSelector = element => { - let selector = element.getAttribute('data-bs-target') - - if (!selector || selector === '#') { - let hrefAttr = element.getAttribute('href') - - // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) { - return null - } - - // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { - hrefAttr = `#${hrefAttr.split('#')[1]}` - } - - 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 -} - const getTransitionDurationFromElement = element => { if (!element) { return 0 @@ -98,51 +71,56 @@ const triggerTransitionEnd = element => { element.dispatchEvent(new Event(TRANSITION_END)) } -const isElement = obj => { - if (!obj || typeof obj !== 'object') { +const isElement = object => { + if (!object || typeof object !== 'object') { return false } - if (typeof obj.jquery !== 'undefined') { - obj = obj[0] + if (typeof object.jquery !== 'undefined') { + object = object[0] } - return typeof obj.nodeType !== 'undefined' + return typeof object.nodeType !== 'undefined' } -const getElement = obj => { +const getElement = object => { // it's a jQuery object or a node element - if (isElement(obj)) { - return obj.jquery ? obj[0] : obj + if (isElement(object)) { + return object.jquery ? object[0] : object } - if (typeof obj === 'string' && obj.length > 0) { - return document.querySelector(obj) + if (typeof object === 'string' && object.length > 0) { + return document.querySelector(parseSelector(object)) } return null } -const typeCheckConfig = (componentName, config, configTypes) => { - for (const property of Object.keys(configTypes)) { - const expectedTypes = configTypes[property] - const value = config[property] - const valueType = value && isElement(value) ? 'element' : toType(value) - - if (!new RegExp(expectedTypes).test(valueType)) { - throw new TypeError( - `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` - ) - } - } -} - const isVisible = element => { if (!isElement(element) || element.getClientRects().length === 0) { return false } - return getComputedStyle(element).getPropertyValue('visibility') === 'visible' + const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible' + // Handle `details` element as its content may falsie appear visible when it is closed + const closedDetails = element.closest('details:not([open])') + + if (!closedDetails) { + return elementIsVisible + } + + if (closedDetails !== element) { + const summary = element.closest('summary') + if (summary && summary.parentNode !== closedDetails) { + return false + } + + if (summary === null) { + return false + } + } + + return elementIsVisible } const isDisabled = element => { @@ -192,17 +170,15 @@ const noop = () => {} * @param {HTMLElement} element * @return void * - * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation + * @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation */ const reflow = element => { element.offsetHeight // eslint-disable-line no-unused-expressions } const getjQuery = () => { - const { jQuery } = window - - if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { - return jQuery + if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { + return window.jQuery } return null @@ -246,10 +222,8 @@ const defineJQueryPlugin = plugin => { }) } -const execute = callback => { - if (typeof callback === 'function') { - callback() - } +const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { + return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue } const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { @@ -291,15 +265,15 @@ const executeAfterTransition = (callback, transitionElement, waitForTransition = * @return {Element|elem} The proper element */ const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + const listLength = list.length 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 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] + return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0] } - const listLength = list.length - index += shouldGetNext ? 1 : -1 if (isCycleAllowed) { @@ -315,10 +289,8 @@ export { executeAfterTransition, findShadowRoot, getElement, - getElementFromSelector, getjQuery, getNextActiveElement, - getSelectorFromElement, getTransitionDurationFromElement, getUID, isDisabled, @@ -327,7 +299,8 @@ export { isVisible, noop, onDOMContentLoaded, + parseSelector, reflow, triggerTransitionEnd, - typeCheckConfig + toType } |
