aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/twbs/bootstrap/js/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/twbs/bootstrap/js/src/util')
-rw-r--r--vendor/twbs/bootstrap/js/src/util/backdrop.js129
-rw-r--r--vendor/twbs/bootstrap/js/src/util/index.js324
-rw-r--r--vendor/twbs/bootstrap/js/src/util/sanitizer.js127
-rw-r--r--vendor/twbs/bootstrap/js/src/util/scrollbar.js97
4 files changed, 677 insertions, 0 deletions
diff --git a/vendor/twbs/bootstrap/js/src/util/backdrop.js b/vendor/twbs/bootstrap/js/src/util/backdrop.js
new file mode 100644
index 000000000..7ba7b4c43
--- /dev/null
+++ b/vendor/twbs/bootstrap/js/src/util/backdrop.js
@@ -0,0 +1,129 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v5.0.2): util/backdrop.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+import EventHandler from '../dom/event-handler'
+import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index'
+
+const Default = {
+ isVisible: true, // if false, we use the backdrop helper without adding any element to the dom
+ isAnimated: false,
+ rootElement: 'body', // give the choice to place backdrop under different elements
+ clickCallback: null
+}
+
+const DefaultType = {
+ isVisible: 'boolean',
+ isAnimated: 'boolean',
+ rootElement: '(element|string)',
+ clickCallback: '(function|null)'
+}
+const NAME = 'backdrop'
+const CLASS_NAME_BACKDROP = 'modal-backdrop'
+const CLASS_NAME_FADE = 'fade'
+const CLASS_NAME_SHOW = 'show'
+
+const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`
+
+class Backdrop {
+ constructor(config) {
+ this._config = this._getConfig(config)
+ this._isAppended = false
+ this._element = null
+ }
+
+ show(callback) {
+ if (!this._config.isVisible) {
+ execute(callback)
+ return
+ }
+
+ this._append()
+
+ if (this._config.isAnimated) {
+ reflow(this._getElement())
+ }
+
+ this._getElement().classList.add(CLASS_NAME_SHOW)
+
+ this._emulateAnimation(() => {
+ execute(callback)
+ })
+ }
+
+ hide(callback) {
+ if (!this._config.isVisible) {
+ execute(callback)
+ return
+ }
+
+ this._getElement().classList.remove(CLASS_NAME_SHOW)
+
+ this._emulateAnimation(() => {
+ this.dispose()
+ execute(callback)
+ })
+ }
+
+ // Private
+
+ _getElement() {
+ if (!this._element) {
+ const backdrop = document.createElement('div')
+ backdrop.className = CLASS_NAME_BACKDROP
+ if (this._config.isAnimated) {
+ backdrop.classList.add(CLASS_NAME_FADE)
+ }
+
+ this._element = backdrop
+ }
+
+ return this._element
+ }
+
+ _getConfig(config) {
+ config = {
+ ...Default,
+ ...(typeof config === 'object' ? config : {})
+ }
+
+ // use getElement() with the default "body" to get a fresh Element on each instantiation
+ config.rootElement = getElement(config.rootElement)
+ typeCheckConfig(NAME, config, DefaultType)
+ return config
+ }
+
+ _append() {
+ if (this._isAppended) {
+ return
+ }
+
+ this._config.rootElement.appendChild(this._getElement())
+
+ EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => {
+ execute(this._config.clickCallback)
+ })
+
+ this._isAppended = true
+ }
+
+ dispose() {
+ if (!this._isAppended) {
+ return
+ }
+
+ EventHandler.off(this._element, EVENT_MOUSEDOWN)
+
+ this._element.remove()
+ this._isAppended = false
+ }
+
+ _emulateAnimation(callback) {
+ executeAfterTransition(callback, this._getElement(), this._config.isAnimated)
+ }
+}
+
+export default Backdrop
diff --git a/vendor/twbs/bootstrap/js/src/util/index.js b/vendor/twbs/bootstrap/js/src/util/index.js
new file mode 100644
index 000000000..7c317b016
--- /dev/null
+++ b/vendor/twbs/bootstrap/js/src/util/index.js
@@ -0,0 +1,324 @@
+import SelectorEngine from '../dom/selector-engine'
+
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v5.0.2): util/index.js
+ * 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
+ * --------------------------------------------------------------------------
+ */
+
+const getUID = prefix => {
+ do {
+ prefix += Math.floor(Math.random() * MAX_UID)
+ } while (document.getElementById(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
+ }
+
+ // Get transition-duration of the element
+ let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
+
+ 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]
+
+ return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
+}
+
+const triggerTransitionEnd = element => {
+ element.dispatchEvent(new Event(TRANSITION_END))
+}
+
+const isElement = obj => {
+ if (!obj || typeof obj !== 'object') {
+ return false
+ }
+
+ if (typeof obj.jquery !== 'undefined') {
+ obj = obj[0]
+ }
+
+ 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
+ }
+
+ if (typeof obj === 'string' && obj.length > 0) {
+ return SelectorEngine.findOne(obj)
+ }
+
+ return null
+}
+
+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)
+
+ 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 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
+ }
+
+ // 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 = () => {}
+
+const reflow = element => element.offsetHeight
+
+const getjQuery = () => {
+ const { jQuery } = window
+
+ if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
+ return jQuery
+ }
+
+ return null
+}
+
+const DOMContentLoadedCallbacks = []
+
+const onDOMContentLoaded = callback => {
+ if (document.readyState === 'loading') {
+ // 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()
+ }
+}
+
+const isRTL = () => document.documentElement.dir === 'rtl'
+
+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
+ $.fn[name].noConflict = () => {
+ $.fn[name] = JQUERY_NO_CONFLICT
+ return plugin.jQueryInterface
+ }
+ }
+ })
+}
+
+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,
+ typeCheckConfig,
+ isVisible,
+ isDisabled,
+ findShadowRoot,
+ noop,
+ getNextActiveElement,
+ reflow,
+ getjQuery,
+ onDOMContentLoaded,
+ isRTL,
+ defineJQueryPlugin,
+ execute,
+ executeAfterTransition
+}
diff --git a/vendor/twbs/bootstrap/js/src/util/sanitizer.js b/vendor/twbs/bootstrap/js/src/util/sanitizer.js
new file mode 100644
index 000000000..49f66417d
--- /dev/null
+++ b/vendor/twbs/bootstrap/js/src/util/sanitizer.js
@@ -0,0 +1,127 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v5.0.2): util/sanitizer.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+const uriAttrs = new Set([
+ 'background',
+ 'cite',
+ 'href',
+ 'itemtype',
+ 'longdesc',
+ 'poster',
+ 'src',
+ 'xlink:href'
+])
+
+const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
+
+/**
+ * A pattern that recognizes a commonly useful subset of URLs that are safe.
+ *
+ * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
+ */
+const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i
+
+/**
+ * A pattern that matches safe data URLs. Only matches image, video and audio types.
+ *
+ * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
+ */
+const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i
+
+const allowedAttribute = (attr, allowedAttributeList) => {
+ const attrName = attr.nodeName.toLowerCase()
+
+ if (allowedAttributeList.includes(attrName)) {
+ if (uriAttrs.has(attrName)) {
+ return Boolean(SAFE_URL_PATTERN.test(attr.nodeValue) || DATA_URL_PATTERN.test(attr.nodeValue))
+ }
+
+ return true
+ }
+
+ const regExp = allowedAttributeList.filter(attrRegex => attrRegex instanceof RegExp)
+
+ // Check if a regular expression validates the attribute.
+ for (let i = 0, len = regExp.length; i < len; i++) {
+ if (regExp[i].test(attrName)) {
+ return true
+ }
+ }
+
+ return false
+}
+
+export const DefaultAllowlist = {
+ // Global attributes allowed on any supplied element below.
+ '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
+ a: ['target', 'href', 'title', 'rel'],
+ area: [],
+ b: [],
+ br: [],
+ col: [],
+ code: [],
+ div: [],
+ em: [],
+ hr: [],
+ h1: [],
+ h2: [],
+ h3: [],
+ h4: [],
+ h5: [],
+ h6: [],
+ i: [],
+ img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
+ li: [],
+ ol: [],
+ p: [],
+ pre: [],
+ s: [],
+ small: [],
+ span: [],
+ sub: [],
+ sup: [],
+ strong: [],
+ u: [],
+ ul: []
+}
+
+export function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) {
+ if (!unsafeHtml.length) {
+ return unsafeHtml
+ }
+
+ if (sanitizeFn && typeof sanitizeFn === 'function') {
+ return sanitizeFn(unsafeHtml)
+ }
+
+ const domParser = new window.DOMParser()
+ const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
+ const allowlistKeys = Object.keys(allowList)
+ const elements = [].concat(...createdDocument.body.querySelectorAll('*'))
+
+ for (let i = 0, len = elements.length; i < len; i++) {
+ const el = elements[i]
+ const elName = el.nodeName.toLowerCase()
+
+ if (!allowlistKeys.includes(elName)) {
+ el.remove()
+
+ continue
+ }
+
+ const attributeList = [].concat(...el.attributes)
+ const allowedAttributes = [].concat(allowList['*'] || [], allowList[elName] || [])
+
+ attributeList.forEach(attr => {
+ if (!allowedAttribute(attr, allowedAttributes)) {
+ el.removeAttribute(attr.nodeName)
+ }
+ })
+ }
+
+ return createdDocument.body.innerHTML
+}
diff --git a/vendor/twbs/bootstrap/js/src/util/scrollbar.js b/vendor/twbs/bootstrap/js/src/util/scrollbar.js
new file mode 100644
index 000000000..fad9766ac
--- /dev/null
+++ b/vendor/twbs/bootstrap/js/src/util/scrollbar.js
@@ -0,0 +1,97 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v5.0.2): util/scrollBar.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+import SelectorEngine from '../dom/selector-engine'
+import Manipulator from '../dom/manipulator'
+import { isElement } from './index'
+
+const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
+const SELECTOR_STICKY_CONTENT = '.sticky-top'
+
+class ScrollBarHelper {
+ constructor() {
+ this._element = document.body
+ }
+
+ getWidth() {
+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
+ const documentWidth = document.documentElement.clientWidth
+ return Math.abs(window.innerWidth - documentWidth)
+ }
+
+ hide() {
+ const width = this.getWidth()
+ this._disableOverFlow()
+ // give padding to element to balance the hidden scrollbar width
+ this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width)
+ // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
+ this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width)
+ this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width)
+ }
+
+ _disableOverFlow() {
+ this._saveInitialAttribute(this._element, 'overflow')
+ this._element.style.overflow = 'hidden'
+ }
+
+ _setElementAttributes(selector, styleProp, callback) {
+ const scrollbarWidth = this.getWidth()
+ const manipulationCallBack = element => {
+ if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
+ return
+ }
+
+ this._saveInitialAttribute(element, styleProp)
+ const calculatedValue = window.getComputedStyle(element)[styleProp]
+ element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px`
+ }
+
+ this._applyManipulationCallback(selector, manipulationCallBack)
+ }
+
+ reset() {
+ this._resetElementAttributes(this._element, 'overflow')
+ this._resetElementAttributes(this._element, 'paddingRight')
+ this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight')
+ this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight')
+ }
+
+ _saveInitialAttribute(element, styleProp) {
+ const actualValue = element.style[styleProp]
+ if (actualValue) {
+ Manipulator.setDataAttribute(element, styleProp, actualValue)
+ }
+ }
+
+ _resetElementAttributes(selector, styleProp) {
+ const manipulationCallBack = element => {
+ const value = Manipulator.getDataAttribute(element, styleProp)
+ if (typeof value === 'undefined') {
+ element.style.removeProperty(styleProp)
+ } else {
+ Manipulator.removeDataAttribute(element, styleProp)
+ element.style[styleProp] = value
+ }
+ }
+
+ this._applyManipulationCallback(selector, manipulationCallBack)
+ }
+
+ _applyManipulationCallback(selector, callBack) {
+ if (isElement(selector)) {
+ callBack(selector)
+ } else {
+ SelectorEngine.find(selector, this._element).forEach(callBack)
+ }
+ }
+
+ isOverflowing() {
+ return this.getWidth() > 0
+ }
+}
+
+export default ScrollBarHelper