aboutsummaryrefslogtreecommitdiff
path: root/js/src/util
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-08-16 20:47:33 -0400
committerGitHub <[email protected]>2024-08-16 20:47:33 -0400
commit6b28433d9cfde435be8ec2bd6cf91e6324d08865 (patch)
tree8343c27b8b95ff5639233e81cf157f92e5688466 /js/src/util
parentd53094ec16ba385faae2973ddee648698b32ab24 (diff)
parent048f56f51460df75e92a2f7b472e1c56baeb68f7 (diff)
downloadbootstrap-main.tar.xz
bootstrap-main.zip
Merge branch 'twbs:main' into mainHEADmain
Diffstat (limited to 'js/src/util')
-rw-r--r--js/src/util/backdrop.js55
-rw-r--r--js/src/util/component-functions.js9
-rw-r--r--js/src/util/config.js65
-rw-r--r--js/src/util/focustrap.js50
-rw-r--r--js/src/util/index.js149
-rw-r--r--js/src/util/sanitizer.js91
-rw-r--r--js/src/util/scrollbar.js61
-rw-r--r--js/src/util/swipe.js40
-rw-r--r--js/src/util/template-factory.js55
9 files changed, 319 insertions, 256 deletions
diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js
index fb1b2776b..82b54900e 100644
--- a/js/src/util/backdrop.js
+++ b/js/src/util/backdrop.js
@@ -1,12 +1,15 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/backdrop.js
+ * Bootstrap util/backdrop.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index'
+import EventHandler from '../dom/event-handler.js'
+import Config from './config.js'
+import {
+ execute, executeAfterTransition, getElement, reflow
+} from './index.js'
/**
* Constants
@@ -19,31 +22,45 @@ const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`
const Default = {
className: 'modal-backdrop',
- isVisible: true, // if false, we use the backdrop helper without adding any element to the dom
+ clickCallback: null,
isAnimated: false,
- rootElement: 'body', // give the choice to place backdrop under different elements
- clickCallback: null
+ isVisible: true, // if false, we use the backdrop helper without adding any element to the dom
+ rootElement: 'body' // give the choice to place backdrop under different elements
}
const DefaultType = {
className: 'string',
- isVisible: 'boolean',
+ clickCallback: '(function|null)',
isAnimated: 'boolean',
- rootElement: '(element|string)',
- clickCallback: '(function|null)'
+ isVisible: 'boolean',
+ rootElement: '(element|string)'
}
/**
* Class definition
*/
-class Backdrop {
+class Backdrop extends Config {
constructor(config) {
+ super()
this._config = this._getConfig(config)
this._isAppended = false
this._element = null
}
+ // Getters
+ static get Default() {
+ return Default
+ }
+
+ static get DefaultType() {
+ return DefaultType
+ }
+
+ static get NAME() {
+ return NAME
+ }
+
// Public
show(callback) {
if (!this._config.isVisible) {
@@ -53,11 +70,12 @@ class Backdrop {
this._append()
+ const element = this._getElement()
if (this._config.isAnimated) {
- reflow(this._getElement())
+ reflow(element)
}
- this._getElement().classList.add(CLASS_NAME_SHOW)
+ element.classList.add(CLASS_NAME_SHOW)
this._emulateAnimation(() => {
execute(callback)
@@ -104,15 +122,9 @@ class Backdrop {
return this._element
}
- _getConfig(config) {
- config = {
- ...Default,
- ...(typeof config === 'object' ? config : {})
- }
-
+ _configAfterMerge(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
}
@@ -121,9 +133,10 @@ class Backdrop {
return
}
- this._config.rootElement.append(this._getElement())
+ const element = this._getElement()
+ this._config.rootElement.append(element)
- EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => {
+ EventHandler.on(element, EVENT_MOUSEDOWN, () => {
execute(this._config.clickCallback)
})
diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js
index bd44c3fdc..4be828f83 100644
--- a/js/src/util/component-functions.js
+++ b/js/src/util/component-functions.js
@@ -1,12 +1,13 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/component-functions.js
+ * Bootstrap util/component-functions.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import { getElementFromSelector, isDisabled } from './index'
+import EventHandler from '../dom/event-handler.js'
+import SelectorEngine from '../dom/selector-engine.js'
+import { isDisabled } from './index.js'
const enableDismissTrigger = (component, method = 'hide') => {
const clickEvent = `click.dismiss${component.EVENT_KEY}`
@@ -21,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => {
return
}
- const target = getElementFromSelector(this) || this.closest(`.${name}`)
+ const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)
const instance = component.getOrCreateInstance(target)
// Method argument is left, for Alert and only, as it doesn't implement the 'hide' method
diff --git a/js/src/util/config.js b/js/src/util/config.js
new file mode 100644
index 000000000..a2b4bfba0
--- /dev/null
+++ b/js/src/util/config.js
@@ -0,0 +1,65 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap util/config.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+import Manipulator from '../dom/manipulator.js'
+import { isElement, toType } from './index.js'
+
+/**
+ * Class definition
+ */
+
+class Config {
+ // Getters
+ static get Default() {
+ return {}
+ }
+
+ static get DefaultType() {
+ return {}
+ }
+
+ static get NAME() {
+ throw new Error('You have to implement the static method "NAME", for each component!')
+ }
+
+ _getConfig(config) {
+ config = this._mergeConfigObj(config)
+ config = this._configAfterMerge(config)
+ this._typeCheckConfig(config)
+ return config
+ }
+
+ _configAfterMerge(config) {
+ return config
+ }
+
+ _mergeConfigObj(config, element) {
+ const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse
+
+ return {
+ ...this.constructor.Default,
+ ...(typeof jsonConfig === 'object' ? jsonConfig : {}),
+ ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),
+ ...(typeof config === 'object' ? config : {})
+ }
+ }
+
+ _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
+ for (const [property, expectedTypes] of Object.entries(configTypes)) {
+ const value = config[property]
+ const valueType = isElement(value) ? 'element' : toType(value)
+
+ if (!new RegExp(expectedTypes).test(valueType)) {
+ throw new TypeError(
+ `${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`
+ )
+ }
+ }
+ }
+}
+
+export default Config
diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js
index a1975f489..158f3d184 100644
--- a/js/src/util/focustrap.js
+++ b/js/src/util/focustrap.js
@@ -1,13 +1,13 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/focustrap.js
+ * Bootstrap util/focustrap.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import SelectorEngine from '../dom/selector-engine'
-import { typeCheckConfig } from './index'
+import EventHandler from '../dom/event-handler.js'
+import SelectorEngine from '../dom/selector-engine.js'
+import Config from './config.js'
/**
* Constants
@@ -24,36 +24,48 @@ const TAB_NAV_FORWARD = 'forward'
const TAB_NAV_BACKWARD = 'backward'
const Default = {
- trapElement: null, // The element to trap focus inside of
- autofocus: true
+ autofocus: true,
+ trapElement: null // The element to trap focus inside of
}
const DefaultType = {
- trapElement: 'element',
- autofocus: 'boolean'
+ autofocus: 'boolean',
+ trapElement: 'element'
}
/**
* Class definition
*/
-class FocusTrap {
+class FocusTrap extends Config {
constructor(config) {
+ super()
this._config = this._getConfig(config)
this._isActive = false
this._lastTabNavDirection = null
}
+ // Getters
+ static get Default() {
+ return Default
+ }
+
+ static get DefaultType() {
+ return DefaultType
+ }
+
+ static get NAME() {
+ return NAME
+ }
+
// Public
activate() {
- const { trapElement, autofocus } = this._config
-
if (this._isActive) {
return
}
- if (autofocus) {
- trapElement.focus()
+ if (this._config.autofocus) {
+ this._config.trapElement.focus()
}
EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop
@@ -74,10 +86,9 @@ class FocusTrap {
// Private
_handleFocusin(event) {
- const { target } = event
const { trapElement } = this._config
- if (target === document || target === trapElement || trapElement.contains(target)) {
+ if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {
return
}
@@ -99,15 +110,6 @@ class FocusTrap {
this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD
}
-
- _getConfig(config) {
- config = {
- ...Default,
- ...(typeof config === 'object' ? config : {})
- }
- typeCheckConfig(NAME, config, DefaultType)
- return config
- }
}
export default FocusTrap
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
}
diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js
index 5a7a68035..3d2883aff 100644
--- a/js/src/util/sanitizer.js
+++ b/js/src/util/sanitizer.js
@@ -1,53 +1,13 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/sanitizer.js
+ * Bootstrap util/sanitizer.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-const uriAttributes = new Set([
- 'background',
- 'cite',
- 'href',
- 'itemtype',
- 'longdesc',
- 'poster',
- 'src',
- 'xlink:href'
-])
-
+// js-docs-start allow-list
const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
-/**
- * A pattern that recognizes a commonly useful subset of URLs that are safe.
- *
- * Shoutout to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts
- */
-const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i
-
-/**
- * A pattern that matches safe data URLs. Only matches image, video and audio types.
- *
- * Shoutout to Angular https://github.com/angular/angular/blob/12.2.x/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 = (attribute, allowedAttributeList) => {
- const attributeName = attribute.nodeName.toLowerCase()
-
- if (allowedAttributeList.includes(attributeName)) {
- if (uriAttributes.has(attributeName)) {
- return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue))
- }
-
- return true
- }
-
- // Check if a regular expression validates the attribute.
- return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
- .some(regex => regex.test(attributeName))
-}
-
export const DefaultAllowlist = {
// Global attributes allowed on any supplied element below.
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
@@ -57,7 +17,10 @@ export const DefaultAllowlist = {
br: [],
col: [],
code: [],
+ dd: [],
div: [],
+ dl: [],
+ dt: [],
em: [],
hr: [],
h1: [],
@@ -81,14 +44,51 @@ export const DefaultAllowlist = {
u: [],
ul: []
}
+// js-docs-end allow-list
+
+const uriAttributes = new Set([
+ 'background',
+ 'cite',
+ 'href',
+ 'itemtype',
+ 'longdesc',
+ 'poster',
+ 'src',
+ 'xlink:href'
+])
+
+/**
+ * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
+ * contexts.
+ *
+ * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
+ */
+// eslint-disable-next-line unicorn/better-regex
+const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
+
+const allowedAttribute = (attribute, allowedAttributeList) => {
+ const attributeName = attribute.nodeName.toLowerCase()
+
+ if (allowedAttributeList.includes(attributeName)) {
+ if (uriAttributes.has(attributeName)) {
+ return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
+ }
+
+ return true
+ }
+
+ // Check if a regular expression validates the attribute.
+ return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
+ .some(regex => regex.test(attributeName))
+}
-export function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) {
+export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
if (!unsafeHtml.length) {
return unsafeHtml
}
- if (sanitizeFn && typeof sanitizeFn === 'function') {
- return sanitizeFn(unsafeHtml)
+ if (sanitizeFunction && typeof sanitizeFunction === 'function') {
+ return sanitizeFunction(unsafeHtml)
}
const domParser = new window.DOMParser()
@@ -100,7 +100,6 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) {
if (!Object.keys(allowList).includes(elementName)) {
element.remove()
-
continue
}
diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js
index 55b7244ab..413f178da 100644
--- a/js/src/util/scrollbar.js
+++ b/js/src/util/scrollbar.js
@@ -1,13 +1,13 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/scrollBar.js
+ * Bootstrap 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'
+import Manipulator from '../dom/manipulator.js'
+import SelectorEngine from '../dom/selector-engine.js'
+import { isElement } from './index.js'
/**
* Constants
@@ -15,6 +15,8 @@ import { isElement } from './index'
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
const SELECTOR_STICKY_CONTENT = '.sticky-top'
+const PROPERTY_PADDING = 'padding-right'
+const PROPERTY_MARGIN = 'margin-right'
/**
* Class definition
@@ -36,17 +38,17 @@ class ScrollBarHelper {
const width = this.getWidth()
this._disableOverFlow()
// give padding to element to balance the hidden scrollbar width
- this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width)
+ this._setElementAttributes(this._element, PROPERTY_PADDING, 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)
+ this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
+ this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)
}
reset() {
this._resetElementAttributes(this._element, 'overflow')
- this._resetElementAttributes(this._element, 'paddingRight')
- this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight')
- this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight')
+ this._resetElementAttributes(this._element, PROPERTY_PADDING)
+ this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)
+ this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)
}
isOverflowing() {
@@ -59,37 +61,39 @@ class ScrollBarHelper {
this._element.style.overflow = 'hidden'
}
- _setElementAttributes(selector, styleProp, callback) {
+ _setElementAttributes(selector, styleProperty, 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._saveInitialAttribute(element, styleProperty)
+ const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)
+ element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)
}
this._applyManipulationCallback(selector, manipulationCallBack)
}
- _saveInitialAttribute(element, styleProp) {
- const actualValue = element.style[styleProp]
+ _saveInitialAttribute(element, styleProperty) {
+ const actualValue = element.style.getPropertyValue(styleProperty)
if (actualValue) {
- Manipulator.setDataAttribute(element, styleProp, actualValue)
+ Manipulator.setDataAttribute(element, styleProperty, actualValue)
}
}
- _resetElementAttributes(selector, styleProp) {
+ _resetElementAttributes(selector, styleProperty) {
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
+ const value = Manipulator.getDataAttribute(element, styleProperty)
+ // We only want to remove the property if the value is `null`; the value can also be zero
+ if (value === null) {
+ element.style.removeProperty(styleProperty)
+ return
}
+
+ Manipulator.removeDataAttribute(element, styleProperty)
+ element.style.setProperty(styleProperty, value)
}
this._applyManipulationCallback(selector, manipulationCallBack)
@@ -98,10 +102,11 @@ class ScrollBarHelper {
_applyManipulationCallback(selector, callBack) {
if (isElement(selector)) {
callBack(selector)
- } else {
- for (const sel of SelectorEngine.find(selector, this._element)) {
- callBack(sel)
- }
+ return
+ }
+
+ for (const sel of SelectorEngine.find(selector, this._element)) {
+ callBack(sel)
}
}
}
diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js
index 87a5f7f5a..d2f708711 100644
--- a/js/src/util/swipe.js
+++ b/js/src/util/swipe.js
@@ -1,12 +1,13 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/swipe.js
+ * Bootstrap util/swipe.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import EventHandler from '../dom/event-handler'
-import { execute, typeCheckConfig } from './index'
+import EventHandler from '../dom/event-handler.js'
+import Config from './config.js'
+import { execute } from './index.js'
/**
* Constants
@@ -25,23 +26,24 @@ const CLASS_NAME_POINTER_EVENT = 'pointer-event'
const SWIPE_THRESHOLD = 40
const Default = {
+ endCallback: null,
leftCallback: null,
- rightCallback: null,
- endCallback: null
+ rightCallback: null
}
const DefaultType = {
+ endCallback: '(function|null)',
leftCallback: '(function|null)',
- rightCallback: '(function|null)',
- endCallback: '(function|null)'
+ rightCallback: '(function|null)'
}
/**
* Class definition
*/
-class Swipe {
+class Swipe extends Config {
constructor(element, config) {
+ super()
this._element = element
if (!element || !Swipe.isSupported()) {
@@ -54,6 +56,19 @@ class Swipe {
this._initEvents()
}
+ // Getters
+ static get Default() {
+ return Default
+ }
+
+ static get DefaultType() {
+ return DefaultType
+ }
+
+ static get NAME() {
+ return NAME
+ }
+
// Public
dispose() {
EventHandler.off(this._element, EVENT_KEY)
@@ -118,15 +133,6 @@ class Swipe {
}
}
- _getConfig(config) {
- config = {
- ...Default,
- ...(typeof config === 'object' ? config : {})
- }
- typeCheckConfig(NAME, config, DefaultType)
- return config
- }
-
_eventIsPointerPenTouch(event) {
return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)
}
diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js
index a9cee1086..6d1532b4d 100644
--- a/js/src/util/template-factory.js
+++ b/js/src/util/template-factory.js
@@ -1,13 +1,14 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): util/template-factory.js
+ * Bootstrap util/template-factory.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import { DefaultAllowlist, sanitizeHtml } from './sanitizer'
-import { getElement, isElement, typeCheckConfig } from '../util/index'
-import SelectorEngine from '../dom/selector-engine'
+import SelectorEngine from '../dom/selector-engine.js'
+import Config from './config.js'
+import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'
+import { execute, getElement, isElement } from './index.js'
/**
* Constants
@@ -16,48 +17,53 @@ import SelectorEngine from '../dom/selector-engine'
const NAME = 'TemplateFactory'
const Default = {
- extraClass: '',
- template: '<div></div>',
+ allowList: DefaultAllowlist,
content: {}, // { selector : text , selector2 : text2 , }
+ extraClass: '',
html: false,
sanitize: true,
sanitizeFn: null,
- allowList: DefaultAllowlist
+ template: '<div></div>'
}
const DefaultType = {
- extraClass: '(string|function)',
- template: 'string',
+ allowList: 'object',
content: 'object',
+ extraClass: '(string|function)',
html: 'boolean',
sanitize: 'boolean',
sanitizeFn: '(null|function)',
- allowList: 'object'
+ template: 'string'
}
const DefaultContentType = {
- selector: '(string|element)',
- entry: '(string|element|function|null)'
+ entry: '(string|element|function|null)',
+ selector: '(string|element)'
}
/**
* Class definition
*/
-class TemplateFactory {
+class TemplateFactory extends Config {
constructor(config) {
+ super()
this._config = this._getConfig(config)
}
// Getters
- static get NAME() {
- return NAME
- }
-
static get Default() {
return Default
}
+ static get DefaultType() {
+ return DefaultType
+ }
+
+ static get NAME() {
+ return NAME
+ }
+
// Public
getContent() {
return Object.values(this._config.content)
@@ -94,21 +100,14 @@ class TemplateFactory {
}
// Private
- _getConfig(config) {
- config = {
- ...Default,
- ...(typeof config === 'object' ? config : {})
- }
-
- typeCheckConfig(NAME, config, DefaultType)
+ _typeCheckConfig(config) {
+ super._typeCheckConfig(config)
this._checkContent(config.content)
-
- return config
}
_checkContent(arg) {
for (const [selector, content] of Object.entries(arg)) {
- typeCheckConfig(NAME, { selector, entry: content }, DefaultContentType)
+ super._typeCheckConfig({ selector, entry: content }, DefaultContentType)
}
}
@@ -144,7 +143,7 @@ class TemplateFactory {
}
_resolvePossibleFunction(arg) {
- return typeof arg === 'function' ? arg(this) : arg
+ return execute(arg, [undefined, this])
}
_putElementInTemplate(element, templateElement) {