diff options
Diffstat (limited to 'js/src/util/index.js')
| -rw-r--r-- | js/src/util/index.js | 76 |
1 files changed, 63 insertions, 13 deletions
diff --git a/js/src/util/index.js b/js/src/util/index.js index 96cadc65b..c27c470e9 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.0.0-alpha3): util/index.js + * Bootstrap (v5.0.0-beta3): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -36,7 +36,20 @@ const getSelector = element => { let selector = element.getAttribute('data-bs-target') if (!selector || selector === '#') { - const hrefAttr = element.getAttribute('href') + 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 } @@ -111,15 +124,12 @@ 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) + 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}".`) + throw new TypeError( + `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` + ) } }) } @@ -141,6 +151,22 @@ const isVisible = element => { return false } +const isDisabled = element => { + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return true + } + + if (element.classList.contains('disabled')) { + return true + } + + if (typeof element.disabled !== 'undefined') { + return element.disabled + } + + return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false' +} + const findShadowRoot = element => { if (!document.documentElement.attachShadow) { return null @@ -164,7 +190,7 @@ const findShadowRoot = element => { return findShadowRoot(element.parentNode) } -const noop = () => function () {} +const noop = () => {} const reflow = element => element.offsetHeight @@ -186,10 +212,31 @@ const onDOMContentLoaded = callback => { } } -const isRTL = document.documentElement.dir === 'rtl' +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 + } + } + }) +} + +const execute = callback => { + if (typeof callback === 'function') { + callback() + } +} export { - TRANSITION_END, getUID, getSelectorFromElement, getElementFromSelector, @@ -199,10 +246,13 @@ export { emulateTransitionEnd, typeCheckConfig, isVisible, + isDisabled, findShadowRoot, noop, reflow, getjQuery, onDOMContentLoaded, - isRTL + isRTL, + defineJQueryPlugin, + execute } |
