aboutsummaryrefslogtreecommitdiff
path: root/js/src/dom
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/dom')
-rw-r--r--js/src/dom/data.js2
-rw-r--r--js/src/dom/event-handler.js173
-rw-r--r--js/src/dom/manipulator.js44
-rw-r--r--js/src/dom/selector-engine.js68
4 files changed, 148 insertions, 139 deletions
diff --git a/js/src/dom/data.js b/js/src/dom/data.js
index 4209f3188..407f67e39 100644
--- a/js/src/dom/data.js
+++ b/js/src/dom/data.js
@@ -1,6 +1,6 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): dom/data.js
+ * Bootstrap dom/data.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js
index b9ebce324..561d8751d 100644
--- a/js/src/dom/event-handler.js
+++ b/js/src/dom/event-handler.js
@@ -1,11 +1,11 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): dom/event-handler.js
+ * Bootstrap dom/event-handler.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import { getjQuery } from '../util/index'
+import { getjQuery } from '../util/index.js'
/**
* Constants
@@ -20,7 +20,7 @@ const customEvents = {
mouseenter: 'mouseover',
mouseleave: 'mouseout'
}
-const customEventsRegex = /^(mouseenter|mouseleave)/i
+
const nativeEvents = new Set([
'click',
'dblclick',
@@ -74,12 +74,12 @@ const nativeEvents = new Set([
* Private methods
*/
-function getUidEvent(element, uid) {
+function makeEventUid(element, uid) {
return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++
}
-function getEvent(element) {
- const uid = getUidEvent(element)
+function getElementEvents(element) {
+ const uid = makeEventUid(element)
element.uidEvent = uid
eventRegistry[uid] = eventRegistry[uid] || {}
@@ -89,7 +89,7 @@ function getEvent(element) {
function bootstrapHandler(element, fn) {
return function handler(event) {
- event.delegateTarget = element
+ hydrateObj(event, { delegateTarget: element })
if (handler.oneOff) {
EventHandler.off(element, event.type, fn)
@@ -104,65 +104,52 @@ function bootstrapDelegationHandler(element, selector, fn) {
const domElements = element.querySelectorAll(selector)
for (let { target } = event; target && target !== this; target = target.parentNode) {
- for (let i = domElements.length; i--;) {
- if (domElements[i] === target) {
- event.delegateTarget = target
+ for (const domElement of domElements) {
+ if (domElement !== target) {
+ continue
+ }
- if (handler.oneOff) {
- EventHandler.off(element, event.type, selector, fn)
- }
+ hydrateObj(event, { delegateTarget: target })
- return fn.apply(target, [event])
+ if (handler.oneOff) {
+ EventHandler.off(element, event.type, selector, fn)
}
+
+ return fn.apply(target, [event])
}
}
-
- // To please ESLint
- return null
}
}
-function findHandler(events, handler, delegationSelector = null) {
- const uidEventList = Object.keys(events)
-
- for (const uidEvent of uidEventList) {
- const event = events[uidEvent]
-
- if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {
- return event
- }
- }
-
- return null
+function findHandler(events, callable, delegationSelector = null) {
+ return Object.values(events)
+ .find(event => event.callable === callable && event.delegationSelector === delegationSelector)
}
-function normalizeParams(originalTypeEvent, handler, delegationFn) {
- const delegation = typeof handler === 'string'
- const originalHandler = delegation ? delegationFn : handler
+function normalizeParameters(originalTypeEvent, handler, delegationFunction) {
+ const isDelegated = typeof handler === 'string'
+ // TODO: tooltip passes `false` instead of selector, so we need to check
+ const callable = isDelegated ? delegationFunction : (handler || delegationFunction)
let typeEvent = getTypeEvent(originalTypeEvent)
- const isNative = nativeEvents.has(typeEvent)
- if (!isNative) {
+ if (!nativeEvents.has(typeEvent)) {
typeEvent = originalTypeEvent
}
- return [delegation, originalHandler, typeEvent]
+ return [isDelegated, callable, typeEvent]
}
-function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
+function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {
if (typeof originalTypeEvent !== 'string' || !element) {
return
}
- if (!handler) {
- handler = delegationFn
- delegationFn = null
- }
+ let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)
// in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position
// this prevents the handler from being dispatched the same way as mouseover or mouseout does
- if (customEventsRegex.test(originalTypeEvent)) {
- const wrapFn = fn => {
+ if (originalTypeEvent in customEvents) {
+ const wrapFunction = fn => {
return function (event) {
if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {
return fn.call(this, event)
@@ -170,36 +157,31 @@ function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
}
}
- if (delegationFn) {
- delegationFn = wrapFn(delegationFn)
- } else {
- handler = wrapFn(handler)
- }
+ callable = wrapFunction(callable)
}
- const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
- const events = getEvent(element)
+ const events = getElementEvents(element)
const handlers = events[typeEvent] || (events[typeEvent] = {})
- const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)
+ const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)
- if (previousFn) {
- previousFn.oneOff = previousFn.oneOff && oneOff
+ if (previousFunction) {
+ previousFunction.oneOff = previousFunction.oneOff && oneOff
return
}
- const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
- const fn = delegation ?
- bootstrapDelegationHandler(element, handler, delegationFn) :
- bootstrapHandler(element, handler)
+ const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))
+ const fn = isDelegated ?
+ bootstrapDelegationHandler(element, handler, callable) :
+ bootstrapHandler(element, callable)
- fn.delegationSelector = delegation ? handler : null
- fn.originalHandler = originalHandler
+ fn.delegationSelector = isDelegated ? handler : null
+ fn.callable = callable
fn.oneOff = oneOff
fn.uidEvent = uid
handlers[uid] = fn
- element.addEventListener(typeEvent, fn, delegation)
+ element.addEventListener(typeEvent, fn, isDelegated)
}
function removeHandler(element, events, typeEvent, handler, delegationSelector) {
@@ -216,10 +198,9 @@ function removeHandler(element, events, typeEvent, handler, delegationSelector)
function removeNamespacedHandlers(element, events, typeEvent, namespace) {
const storeElementEvent = events[typeEvent] || {}
- for (const handlerKey of Object.keys(storeElementEvent)) {
+ for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
if (handlerKey.includes(namespace)) {
- const event = storeElementEvent[handlerKey]
- removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
+ removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
}
}
}
@@ -231,31 +212,32 @@ function getTypeEvent(event) {
}
const EventHandler = {
- on(element, event, handler, delegationFn) {
- addHandler(element, event, handler, delegationFn, false)
+ on(element, event, handler, delegationFunction) {
+ addHandler(element, event, handler, delegationFunction, false)
},
- one(element, event, handler, delegationFn) {
- addHandler(element, event, handler, delegationFn, true)
+ one(element, event, handler, delegationFunction) {
+ addHandler(element, event, handler, delegationFunction, true)
},
- off(element, originalTypeEvent, handler, delegationFn) {
+ off(element, originalTypeEvent, handler, delegationFunction) {
if (typeof originalTypeEvent !== 'string' || !element) {
return
}
- const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
+ const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)
const inNamespace = typeEvent !== originalTypeEvent
- const events = getEvent(element)
+ const events = getElementEvents(element)
+ const storeElementEvent = events[typeEvent] || {}
const isNamespace = originalTypeEvent.startsWith('.')
- if (typeof originalHandler !== 'undefined') {
+ if (typeof callable !== 'undefined') {
// Simplest case: handler is passed, remove that listener ONLY.
- if (!events || !events[typeEvent]) {
+ if (!Object.keys(storeElementEvent).length) {
return
}
- removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)
+ removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)
return
}
@@ -265,13 +247,11 @@ const EventHandler = {
}
}
- const storeElementEvent = events[typeEvent] || {}
- for (const keyHandlers of Object.keys(storeElementEvent)) {
+ for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {
const handlerKey = keyHandlers.replace(stripUidRegex, '')
if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
- const event = storeElementEvent[keyHandlers]
- removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
+ removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
}
}
},
@@ -284,13 +264,11 @@ const EventHandler = {
const $ = getjQuery()
const typeEvent = getTypeEvent(event)
const inNamespace = event !== typeEvent
- const isNative = nativeEvents.has(typeEvent)
- let jQueryEvent
+ let jQueryEvent = null
let bubbles = true
let nativeDispatch = true
let defaultPrevented = false
- let evt = null
if (inNamespace && $) {
jQueryEvent = $.Event(event, args)
@@ -301,23 +279,7 @@ const EventHandler = {
defaultPrevented = jQueryEvent.isDefaultPrevented()
}
- if (isNative) {
- evt = document.createEvent('HTMLEvents')
- evt.initEvent(typeEvent, bubbles, true)
- } else {
- evt = new CustomEvent(event, { bubbles, cancelable: true })
- }
-
- // merge custom information in our event
- if (typeof args !== 'undefined') {
- for (const key of Object.keys(args)) {
- Object.defineProperty(evt, key, {
- get() {
- return args[key]
- }
- })
- }
- }
+ const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)
if (defaultPrevented) {
evt.preventDefault()
@@ -327,7 +289,7 @@ const EventHandler = {
element.dispatchEvent(evt)
}
- if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {
+ if (evt.defaultPrevented && jQueryEvent) {
jQueryEvent.preventDefault()
}
@@ -335,4 +297,21 @@ const EventHandler = {
}
}
+function hydrateObj(obj, meta = {}) {
+ for (const [key, value] of Object.entries(meta)) {
+ try {
+ obj[key] = value
+ } catch {
+ Object.defineProperty(obj, key, {
+ configurable: true,
+ get() {
+ return value
+ }
+ })
+ }
+ }
+
+ return obj
+}
+
export default EventHandler
diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js
index a3e9e192a..a7edc9cb8 100644
--- a/js/src/dom/manipulator.js
+++ b/js/src/dom/manipulator.js
@@ -1,28 +1,36 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): dom/manipulator.js
+ * Bootstrap dom/manipulator.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-function normalizeData(val) {
- if (val === 'true') {
+function normalizeData(value) {
+ if (value === 'true') {
return true
}
- if (val === 'false') {
+ if (value === 'false') {
return false
}
- if (val === Number(val).toString()) {
- return Number(val)
+ if (value === Number(value).toString()) {
+ return Number(value)
}
- if (val === '' || val === 'null') {
+ if (value === '' || value === 'null') {
return null
}
- return val
+ if (typeof value !== 'string') {
+ return value
+ }
+
+ try {
+ return JSON.parse(decodeURIComponent(value))
+ } catch {
+ return value
+ }
}
function normalizeDataKey(key) {
@@ -44,11 +52,11 @@ const Manipulator = {
}
const attributes = {}
- const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs'))
+ const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))
for (const key of bsKeys) {
let pureKey = key.replace(/^bs/, '')
- pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)
+ pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)
attributes[pureKey] = normalizeData(element.dataset[key])
}
@@ -57,22 +65,6 @@ const Manipulator = {
getDataAttribute(element, key) {
return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))
- },
-
- offset(element) {
- const rect = element.getBoundingClientRect()
-
- return {
- top: rect.top + window.pageYOffset,
- left: rect.left + window.pageXOffset
- }
- },
-
- position(element) {
- return {
- top: element.offsetTop,
- left: element.offsetLeft
- }
}
}
diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js
index af27dc379..a4d81f3b9 100644
--- a/js/src/dom/selector-engine.js
+++ b/js/src/dom/selector-engine.js
@@ -1,17 +1,36 @@
/**
* --------------------------------------------------------------------------
- * Bootstrap (v5.1.3): dom/selector-engine.js
+ * Bootstrap dom/selector-engine.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
-import { isDisabled, isVisible } from '../util/index'
+import { isDisabled, isVisible, parseSelector } from '../util/index.js'
-/**
- * Constants
- */
+const getSelector = element => {
+ let selector = element.getAttribute('data-bs-target')
+
+ if (!selector || selector === '#') {
+ let hrefAttribute = 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 (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
+ return null
+ }
+
+ // Just in case some CMS puts out a full URL with the anchor appended
+ if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
+ hrefAttribute = `#${hrefAttribute.split('#')[1]}`
+ }
+
+ selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
+ }
-const NODE_TEXT = 3
+ return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null
+}
const SelectorEngine = {
find(selector, element = document.documentElement) {
@@ -28,14 +47,11 @@ const SelectorEngine = {
parents(element, selector) {
const parents = []
- let ancestor = element.parentNode
-
- while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODE_TEXT) {
- if (ancestor.matches(selector)) {
- parents.push(ancestor)
- }
+ let ancestor = element.parentNode.closest(selector)
- ancestor = ancestor.parentNode
+ while (ancestor) {
+ parents.push(ancestor)
+ ancestor = ancestor.parentNode.closest(selector)
}
return parents
@@ -54,7 +70,7 @@ const SelectorEngine = {
return []
},
-
+ // TODO: this is now unused; remove later along with prev()
next(element, selector) {
let next = element.nextElementSibling
@@ -79,9 +95,31 @@ const SelectorEngine = {
'details',
'[tabindex]',
'[contenteditable="true"]'
- ].map(selector => `${selector}:not([tabindex^="-"])`).join(', ')
+ ].map(selector => `${selector}:not([tabindex^="-"])`).join(',')
return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el))
+ },
+
+ getSelectorFromElement(element) {
+ const selector = getSelector(element)
+
+ if (selector) {
+ return SelectorEngine.findOne(selector) ? selector : null
+ }
+
+ return null
+ },
+
+ getElementFromSelector(element) {
+ const selector = getSelector(element)
+
+ return selector ? SelectorEngine.findOne(selector) : null
+ },
+
+ getMultipleElementsFromSelector(element) {
+ const selector = getSelector(element)
+
+ return selector ? SelectorEngine.find(selector) : []
}
}