aboutsummaryrefslogtreecommitdiff
path: root/js/src/util
diff options
context:
space:
mode:
authorPriyansh <[email protected]>2021-11-28 14:27:57 -0500
committerGitHub <[email protected]>2021-11-28 14:27:57 -0500
commitd53094ec16ba385faae2973ddee648698b32ab24 (patch)
tree9fe907f4f5a4ed57fff915526f36eca9dd05f07e /js/src/util
parent52cd86f8710f8049a744b5bcb9f4a7ce19114b6e (diff)
parent5290080d4df3047d04c8a232bca5dc7f8eaa4bc6 (diff)
downloadbootstrap-d53094ec16ba385faae2973ddee648698b32ab24.tar.xz
bootstrap-d53094ec16ba385faae2973ddee648698b32ab24.zip
Merge branch 'twbs:main' into main
Diffstat (limited to 'js/src/util')
-rw-r--r--js/src/util/backdrop.js42
-rw-r--r--js/src/util/component-functions.js2
-rw-r--r--js/src/util/focustrap.js30
-rw-r--r--js/src/util/index.js38
-rw-r--r--js/src/util/sanitizer.js4
-rw-r--r--js/src/util/scrollbar.js34
-rw-r--r--js/src/util/swipe.js140
-rw-r--r--js/src/util/template-factory.js161
8 files changed, 388 insertions, 63 deletions
diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js
index e5ca0c860..fb1b2776b 100644
--- a/js/src/util/backdrop.js
+++ b/js/src/util/backdrop.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.2): util/backdrop.js
+ * Bootstrap (v5.1.3): util/backdrop.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
@@ -8,6 +8,15 @@
import EventHandler from '../dom/event-handler'
import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index'
+/**
+ * Constants
+ */
+
+const NAME = 'backdrop'
+const CLASS_NAME_FADE = 'fade'
+const CLASS_NAME_SHOW = 'show'
+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
@@ -23,11 +32,10 @@ const DefaultType = {
rootElement: '(element|string)',
clickCallback: '(function|null)'
}
-const NAME = 'backdrop'
-const CLASS_NAME_FADE = 'fade'
-const CLASS_NAME_SHOW = 'show'
-const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`
+/**
+ * Class definition
+ */
class Backdrop {
constructor(config) {
@@ -36,6 +44,7 @@ class Backdrop {
this._element = null
}
+ // Public
show(callback) {
if (!this._config.isVisible) {
execute(callback)
@@ -69,8 +78,18 @@ class Backdrop {
})
}
- // Private
+ dispose() {
+ if (!this._isAppended) {
+ return
+ }
+ EventHandler.off(this._element, EVENT_MOUSEDOWN)
+
+ this._element.remove()
+ this._isAppended = false
+ }
+
+ // Private
_getElement() {
if (!this._element) {
const backdrop = document.createElement('div')
@@ -111,17 +130,6 @@ class Backdrop {
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)
}
diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js
index c678ecadf..bd44c3fdc 100644
--- a/js/src/util/component-functions.js
+++ b/js/src/util/component-functions.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.2): util/component-functions.js
+ * Bootstrap (v5.1.3): util/component-functions.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js
index 500a5b3bc..a1975f489 100644
--- a/js/src/util/focustrap.js
+++ b/js/src/util/focustrap.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.2): util/focustrap.js
+ * Bootstrap (v5.1.3): util/focustrap.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
@@ -9,15 +9,9 @@ import EventHandler from '../dom/event-handler'
import SelectorEngine from '../dom/selector-engine'
import { typeCheckConfig } from './index'
-const Default = {
- trapElement: null, // The element to trap focus inside of
- autofocus: true
-}
-
-const DefaultType = {
- trapElement: 'element',
- autofocus: 'boolean'
-}
+/**
+ * Constants
+ */
const NAME = 'focustrap'
const DATA_KEY = 'bs.focustrap'
@@ -29,6 +23,20 @@ const TAB_KEY = 'Tab'
const TAB_NAV_FORWARD = 'forward'
const TAB_NAV_BACKWARD = 'backward'
+const Default = {
+ trapElement: null, // The element to trap focus inside of
+ autofocus: true
+}
+
+const DefaultType = {
+ trapElement: 'element',
+ autofocus: 'boolean'
+}
+
+/**
+ * Class definition
+ */
+
class FocusTrap {
constructor(config) {
this._config = this._getConfig(config)
@@ -36,6 +44,7 @@ class FocusTrap {
this._lastTabNavDirection = null
}
+ // Public
activate() {
const { trapElement, autofocus } = this._config
@@ -64,7 +73,6 @@ class FocusTrap {
}
// Private
-
_handleFocusin(event) {
const { target } = event
const { trapElement } = this._config
diff --git a/js/src/util/index.js b/js/src/util/index.js
index 0ac4ed263..0ba6ce6f8 100644
--- a/js/src/util/index.js
+++ b/js/src/util/index.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.2): util/index.js
+ * Bootstrap (v5.1.3): util/index.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
@@ -19,9 +19,7 @@ const toType = obj => {
}
/**
- * --------------------------------------------------------------------------
- * Public Util Api
- * --------------------------------------------------------------------------
+ * Public Util API
*/
const getUID = prefix => {
@@ -113,7 +111,8 @@ const isElement = obj => {
}
const getElement = obj => {
- if (isElement(obj)) { // it's a jQuery object or a node element
+ // it's a jQuery object or a node element
+ if (isElement(obj)) {
return obj.jquery ? obj[0] : obj
}
@@ -196,8 +195,7 @@ const noop = () => {}
* @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
+ element.offsetHeight // eslint-disable-line no-unused-expressions
}
const getjQuery = () => {
@@ -312,24 +310,24 @@ const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed
}
export {
+ defineJQueryPlugin,
+ execute,
+ executeAfterTransition,
+ findShadowRoot,
getElement,
- getUID,
- getSelectorFromElement,
getElementFromSelector,
+ getjQuery,
+ getNextActiveElement,
+ getSelectorFromElement,
getTransitionDurationFromElement,
- triggerTransitionEnd,
+ getUID,
+ isDisabled,
isElement,
- typeCheckConfig,
+ isRTL,
isVisible,
- isDisabled,
- findShadowRoot,
noop,
- getNextActiveElement,
- reflow,
- getjQuery,
onDOMContentLoaded,
- isRTL,
- defineJQueryPlugin,
- execute,
- executeAfterTransition
+ reflow,
+ triggerTransitionEnd,
+ typeCheckConfig
}
diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js
index f5a8287cd..5a7a68035 100644
--- a/js/src/util/sanitizer.js
+++ b/js/src/util/sanitizer.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.2): util/sanitizer.js
+ * Bootstrap (v5.1.3): util/sanitizer.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
@@ -45,7 +45,7 @@ const allowedAttribute = (attribute, allowedAttributeList) => {
// Check if a regular expression validates the attribute.
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
- .every(regex => regex.test(attributeName))
+ .some(regex => regex.test(attributeName))
}
export const DefaultAllowlist = {
diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js
index f9a2d992d..55b7244ab 100644
--- a/js/src/util/scrollbar.js
+++ b/js/src/util/scrollbar.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.2): util/scrollBar.js
+ * Bootstrap (v5.1.3): util/scrollBar.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
@@ -9,14 +9,23 @@ import SelectorEngine from '../dom/selector-engine'
import Manipulator from '../dom/manipulator'
import { isElement } from './index'
+/**
+ * Constants
+ */
+
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
const SELECTOR_STICKY_CONTENT = '.sticky-top'
+/**
+ * Class definition
+ */
+
class ScrollBarHelper {
constructor() {
this._element = document.body
}
+ // Public
getWidth() {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
const documentWidth = document.documentElement.clientWidth
@@ -33,6 +42,18 @@ class ScrollBarHelper {
this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', 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')
+ }
+
+ isOverflowing() {
+ return this.getWidth() > 0
+ }
+
+ // Private
_disableOverFlow() {
this._saveInitialAttribute(this._element, 'overflow')
this._element.style.overflow = 'hidden'
@@ -53,13 +74,6 @@ class ScrollBarHelper {
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) {
@@ -90,10 +104,6 @@ class ScrollBarHelper {
}
}
}
-
- isOverflowing() {
- return this.getWidth() > 0
- }
}
export default ScrollBarHelper
diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js
new file mode 100644
index 000000000..87a5f7f5a
--- /dev/null
+++ b/js/src/util/swipe.js
@@ -0,0 +1,140 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v5.1.3): 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'
+
+/**
+ * Constants
+ */
+
+const NAME = 'swipe'
+const EVENT_KEY = '.bs.swipe'
+const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
+const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
+const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
+const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
+const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
+const POINTER_TYPE_TOUCH = 'touch'
+const POINTER_TYPE_PEN = 'pen'
+const CLASS_NAME_POINTER_EVENT = 'pointer-event'
+const SWIPE_THRESHOLD = 40
+
+const Default = {
+ leftCallback: null,
+ rightCallback: null,
+ endCallback: null
+}
+
+const DefaultType = {
+ leftCallback: '(function|null)',
+ rightCallback: '(function|null)',
+ endCallback: '(function|null)'
+}
+
+/**
+ * Class definition
+ */
+
+class Swipe {
+ constructor(element, config) {
+ this._element = element
+
+ if (!element || !Swipe.isSupported()) {
+ return
+ }
+
+ this._config = this._getConfig(config)
+ this._deltaX = 0
+ this._supportPointerEvents = Boolean(window.PointerEvent)
+ this._initEvents()
+ }
+
+ // Public
+ dispose() {
+ EventHandler.off(this._element, EVENT_KEY)
+ }
+
+ // Private
+ _start(event) {
+ if (!this._supportPointerEvents) {
+ this._deltaX = event.touches[0].clientX
+
+ return
+ }
+
+ if (this._eventIsPointerPenTouch(event)) {
+ this._deltaX = event.clientX
+ }
+ }
+
+ _end(event) {
+ if (this._eventIsPointerPenTouch(event)) {
+ this._deltaX = event.clientX - this._deltaX
+ }
+
+ this._handleSwipe()
+ execute(this._config.endCallback)
+ }
+
+ _move(event) {
+ this._deltaX = event.touches && event.touches.length > 1 ?
+ 0 :
+ event.touches[0].clientX - this._deltaX
+ }
+
+ _handleSwipe() {
+ const absDeltaX = Math.abs(this._deltaX)
+
+ if (absDeltaX <= SWIPE_THRESHOLD) {
+ return
+ }
+
+ const direction = absDeltaX / this._deltaX
+
+ this._deltaX = 0
+
+ if (!direction) {
+ return
+ }
+
+ execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)
+ }
+
+ _initEvents() {
+ if (this._supportPointerEvents) {
+ EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))
+ EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))
+
+ this._element.classList.add(CLASS_NAME_POINTER_EVENT)
+ } else {
+ EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))
+ EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))
+ EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))
+ }
+ }
+
+ _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)
+ }
+
+ // Static
+ static isSupported() {
+ return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
+ }
+}
+
+export default Swipe
diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js
new file mode 100644
index 000000000..a9cee1086
--- /dev/null
+++ b/js/src/util/template-factory.js
@@ -0,0 +1,161 @@
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v5.1.3): 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'
+
+/**
+ * Constants
+ */
+
+const NAME = 'TemplateFactory'
+
+const Default = {
+ extraClass: '',
+ template: '<div></div>',
+ content: {}, // { selector : text , selector2 : text2 , }
+ html: false,
+ sanitize: true,
+ sanitizeFn: null,
+ allowList: DefaultAllowlist
+}
+
+const DefaultType = {
+ extraClass: '(string|function)',
+ template: 'string',
+ content: 'object',
+ html: 'boolean',
+ sanitize: 'boolean',
+ sanitizeFn: '(null|function)',
+ allowList: 'object'
+}
+
+const DefaultContentType = {
+ selector: '(string|element)',
+ entry: '(string|element|function|null)'
+}
+
+/**
+ * Class definition
+ */
+
+class TemplateFactory {
+ constructor(config) {
+ this._config = this._getConfig(config)
+ }
+
+ // Getters
+ static get NAME() {
+ return NAME
+ }
+
+ static get Default() {
+ return Default
+ }
+
+ // Public
+ getContent() {
+ return Object.values(this._config.content)
+ .map(config => this._resolvePossibleFunction(config))
+ .filter(Boolean)
+ }
+
+ hasContent() {
+ return this.getContent().length > 0
+ }
+
+ changeContent(content) {
+ this._checkContent(content)
+ this._config.content = { ...this._config.content, ...content }
+ return this
+ }
+
+ toHtml() {
+ const templateWrapper = document.createElement('div')
+ templateWrapper.innerHTML = this._maybeSanitize(this._config.template)
+
+ for (const [selector, text] of Object.entries(this._config.content)) {
+ this._setContent(templateWrapper, text, selector)
+ }
+
+ const template = templateWrapper.children[0]
+ const extraClass = this._resolvePossibleFunction(this._config.extraClass)
+
+ if (extraClass) {
+ template.classList.add(...extraClass.split(' '))
+ }
+
+ return template
+ }
+
+ // Private
+ _getConfig(config) {
+ config = {
+ ...Default,
+ ...(typeof config === 'object' ? config : {})
+ }
+
+ typeCheckConfig(NAME, config, DefaultType)
+ this._checkContent(config.content)
+
+ return config
+ }
+
+ _checkContent(arg) {
+ for (const [selector, content] of Object.entries(arg)) {
+ typeCheckConfig(NAME, { selector, entry: content }, DefaultContentType)
+ }
+ }
+
+ _setContent(template, content, selector) {
+ const templateElement = SelectorEngine.findOne(selector, template)
+
+ if (!templateElement) {
+ return
+ }
+
+ content = this._resolvePossibleFunction(content)
+
+ if (!content) {
+ templateElement.remove()
+ return
+ }
+
+ if (isElement(content)) {
+ this._putElementInTemplate(getElement(content), templateElement)
+ return
+ }
+
+ if (this._config.html) {
+ templateElement.innerHTML = this._maybeSanitize(content)
+ return
+ }
+
+ templateElement.textContent = content
+ }
+
+ _maybeSanitize(arg) {
+ return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg
+ }
+
+ _resolvePossibleFunction(arg) {
+ return typeof arg === 'function' ? arg(this) : arg
+ }
+
+ _putElementInTemplate(element, templateElement) {
+ if (this._config.html) {
+ templateElement.innerHTML = ''
+ templateElement.append(element)
+ return
+ }
+
+ templateElement.textContent = element.textContent
+ }
+}
+
+export default TemplateFactory