mirror of
https://bitbucket.org/bellord13/kit.git
synced 2026-11-13 19:06:11 +03:00
1346 lines
44 KiB
JavaScript
1346 lines
44 KiB
JavaScript
kit.PluginManager.register('bsCore', function(module, exports, require, global){
|
|
|
|
/**
|
|
* @class kit.bsCore
|
|
*/
|
|
var bsCore = {};
|
|
var dom = {};
|
|
var util = {};
|
|
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): dom/data.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
|
|
dom.Data = function(){
|
|
const elementMap = new Map()
|
|
return {
|
|
set(element, key, instance) {
|
|
if (!elementMap.has(element)) {
|
|
elementMap.set(element, new Map())
|
|
}
|
|
|
|
const instanceMap = elementMap.get(element)
|
|
|
|
// make it clear we only want one instance per element
|
|
// can be removed later when multiple key/instances are fine to be used
|
|
if (!instanceMap.has(key) && instanceMap.size !== 0) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)
|
|
return
|
|
}
|
|
|
|
instanceMap.set(key, instance)
|
|
},
|
|
|
|
get(element, key) {
|
|
if (elementMap.has(element)) {
|
|
return elementMap.get(element).get(key) || null
|
|
}
|
|
|
|
return null
|
|
},
|
|
|
|
remove(element, key) {
|
|
if (!elementMap.has(element)) {
|
|
return
|
|
}
|
|
|
|
const instanceMap = elementMap.get(element)
|
|
|
|
instanceMap.delete(key)
|
|
|
|
// free up element references if there are no instances left for an element
|
|
if (instanceMap.size === 0) {
|
|
elementMap.delete(element)
|
|
}
|
|
}
|
|
}
|
|
}();
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): dom/selector-engine.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
dom.SelectorEngine = function(){
|
|
|
|
const NODE_TEXT = 3
|
|
|
|
const SelectorEngine = {
|
|
find(selector, element = document.documentElement) {
|
|
return [].concat(...Element.prototype.querySelectorAll.call(element, selector))
|
|
},
|
|
|
|
findOne(selector, element = document.documentElement) {
|
|
return Element.prototype.querySelector.call(element, selector)
|
|
},
|
|
|
|
children(element, selector) {
|
|
return [].concat(...element.children)
|
|
.filter(child => child.matches(selector))
|
|
},
|
|
|
|
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)
|
|
}
|
|
|
|
ancestor = ancestor.parentNode
|
|
}
|
|
|
|
return parents
|
|
},
|
|
|
|
prev(element, selector) {
|
|
let previous = element.previousElementSibling
|
|
|
|
while (previous) {
|
|
if (previous.matches(selector)) {
|
|
return [previous]
|
|
}
|
|
|
|
previous = previous.previousElementSibling
|
|
}
|
|
|
|
return []
|
|
},
|
|
|
|
next(element, selector) {
|
|
let next = element.nextElementSibling
|
|
|
|
while (next) {
|
|
if (next.matches(selector)) {
|
|
return [next]
|
|
}
|
|
|
|
next = next.nextElementSibling
|
|
}
|
|
|
|
return []
|
|
}
|
|
}
|
|
return SelectorEngine;
|
|
}();
|
|
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): dom/manipulator.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
dom.Manipulator = function(){
|
|
function normalizeData(val) {
|
|
if (val === 'true') {
|
|
return true
|
|
}
|
|
|
|
if (val === 'false') {
|
|
return false
|
|
}
|
|
|
|
if (val === Number(val).toString()) {
|
|
return Number(val)
|
|
}
|
|
|
|
if (val === '' || val === 'null') {
|
|
return null
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
function normalizeDataKey(key) {
|
|
return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)
|
|
}
|
|
|
|
const Manipulator = {
|
|
setDataAttribute(element, key, value) {
|
|
element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)
|
|
},
|
|
|
|
removeDataAttribute(element, key) {
|
|
element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)
|
|
},
|
|
|
|
getDataAttributes(element) {
|
|
if (!element) {
|
|
return {}
|
|
}
|
|
|
|
const attributes = {}
|
|
|
|
Object.keys(element.dataset)
|
|
.filter(key => key.startsWith('bs'))
|
|
.forEach(key => {
|
|
let pureKey = key.replace(/^bs/, '')
|
|
pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)
|
|
attributes[pureKey] = normalizeData(element.dataset[key])
|
|
})
|
|
|
|
return attributes
|
|
},
|
|
|
|
getDataAttribute(element, key) {
|
|
return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))
|
|
},
|
|
|
|
offset(element) {
|
|
const rect = element.getBoundingClientRect()
|
|
|
|
return {
|
|
top: rect.top + document.body.scrollTop,
|
|
left: rect.left + document.body.scrollLeft
|
|
}
|
|
},
|
|
|
|
position(element) {
|
|
return {
|
|
top: element.offsetTop,
|
|
left: element.offsetLeft
|
|
}
|
|
}
|
|
}
|
|
return Manipulator;
|
|
}();
|
|
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): util/index.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
|
|
util.index = function(SelectorEngine){
|
|
|
|
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} = global
|
|
|
|
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))]
|
|
}
|
|
|
|
return {
|
|
getElement,
|
|
getUID,
|
|
getSelectorFromElement,
|
|
getElementFromSelector,
|
|
getTransitionDurationFromElement,
|
|
triggerTransitionEnd,
|
|
isElement,
|
|
typeCheckConfig,
|
|
isVisible,
|
|
isDisabled,
|
|
findShadowRoot,
|
|
noop,
|
|
getNextActiveElement,
|
|
reflow,
|
|
getjQuery,
|
|
onDOMContentLoaded,
|
|
isRTL,
|
|
defineJQueryPlugin,
|
|
execute,
|
|
executeAfterTransition
|
|
}
|
|
}(dom.SelectorEngine);
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): dom/event-handler.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
|
|
dom.EventHandler = function(getjQuery){
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
const namespaceRegex = /[^.]*(?=\..*)\.|.*/
|
|
const stripNameRegex = /\..*/
|
|
const stripUidRegex = /::\d+$/
|
|
const eventRegistry = {} // Events storage
|
|
let uidEvent = 1
|
|
const customEvents = {
|
|
mouseenter: 'mouseover',
|
|
mouseleave: 'mouseout'
|
|
}
|
|
const customEventsRegex = /^(mouseenter|mouseleave)/i
|
|
const nativeEvents = new Set([
|
|
'click',
|
|
'dblclick',
|
|
'mouseup',
|
|
'mousedown',
|
|
'contextmenu',
|
|
'mousewheel',
|
|
'DOMMouseScroll',
|
|
'mouseover',
|
|
'mouseout',
|
|
'mousemove',
|
|
'selectstart',
|
|
'selectend',
|
|
'keydown',
|
|
'keypress',
|
|
'keyup',
|
|
'orientationchange',
|
|
'touchstart',
|
|
'touchmove',
|
|
'touchend',
|
|
'touchcancel',
|
|
'pointerdown',
|
|
'pointermove',
|
|
'pointerup',
|
|
'pointerleave',
|
|
'pointercancel',
|
|
'gesturestart',
|
|
'gesturechange',
|
|
'gestureend',
|
|
'focus',
|
|
'blur',
|
|
'change',
|
|
'reset',
|
|
'select',
|
|
'submit',
|
|
'focusin',
|
|
'focusout',
|
|
'load',
|
|
'unload',
|
|
'beforeunload',
|
|
'resize',
|
|
'move',
|
|
'DOMContentLoaded',
|
|
'readystatechange',
|
|
'error',
|
|
'abort',
|
|
'scroll'
|
|
])
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Private methods
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
function getUidEvent(element, uid) {
|
|
return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++
|
|
}
|
|
|
|
function getEvent(element) {
|
|
const uid = getUidEvent(element)
|
|
|
|
element.uidEvent = uid
|
|
eventRegistry[uid] = eventRegistry[uid] || {}
|
|
|
|
return eventRegistry[uid]
|
|
}
|
|
|
|
function bootstrapHandler(element, fn) {
|
|
return function handler(event) {
|
|
event.delegateTarget = element
|
|
|
|
if (handler.oneOff) {
|
|
EventHandler.off(element, event.type, fn)
|
|
}
|
|
|
|
return fn.apply(element, [event])
|
|
}
|
|
}
|
|
|
|
function bootstrapDelegationHandler(element, selector, fn) {
|
|
return function handler(event) {
|
|
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
|
|
|
|
if (handler.oneOff) {
|
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
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 (let i = 0, len = uidEventList.length; i < len; i++) {
|
|
const event = events[uidEventList[i]]
|
|
|
|
if (event.originalHandler === handler && event.delegationSelector === delegationSelector) {
|
|
return event
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function normalizeParams(originalTypeEvent, handler, delegationFn) {
|
|
const delegation = typeof handler === 'string'
|
|
const originalHandler = delegation ? delegationFn : handler
|
|
|
|
let typeEvent = getTypeEvent(originalTypeEvent)
|
|
const isNative = nativeEvents.has(typeEvent)
|
|
|
|
if (!isNative) {
|
|
typeEvent = originalTypeEvent
|
|
}
|
|
|
|
return [delegation, originalHandler, typeEvent]
|
|
}
|
|
|
|
function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) {
|
|
if (typeof originalTypeEvent !== 'string' || !element) {
|
|
return
|
|
}
|
|
|
|
if (!handler) {
|
|
handler = delegationFn
|
|
delegationFn = null
|
|
}
|
|
|
|
// 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 => {
|
|
return function (event) {
|
|
if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {
|
|
return fn.call(this, event)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delegationFn) {
|
|
delegationFn = wrapFn(delegationFn)
|
|
} else {
|
|
handler = wrapFn(handler)
|
|
}
|
|
}
|
|
|
|
const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
|
|
const events = getEvent(element)
|
|
const handlers = events[typeEvent] || (events[typeEvent] = {})
|
|
const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null)
|
|
|
|
if (previousFn) {
|
|
previousFn.oneOff = previousFn.oneOff && oneOff
|
|
|
|
return
|
|
}
|
|
|
|
const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, ''))
|
|
const fn = delegation ?
|
|
bootstrapDelegationHandler(element, handler, delegationFn) :
|
|
bootstrapHandler(element, handler)
|
|
|
|
fn.delegationSelector = delegation ? handler : null
|
|
fn.originalHandler = originalHandler
|
|
fn.oneOff = oneOff
|
|
fn.uidEvent = uid
|
|
handlers[uid] = fn
|
|
|
|
element.addEventListener(typeEvent, fn, delegation)
|
|
}
|
|
|
|
function removeHandler(element, events, typeEvent, handler, delegationSelector) {
|
|
const fn = findHandler(events[typeEvent], handler, delegationSelector)
|
|
|
|
if (!fn) {
|
|
return
|
|
}
|
|
|
|
element.removeEventListener(typeEvent, fn, Boolean(delegationSelector))
|
|
delete events[typeEvent][fn.uidEvent]
|
|
}
|
|
|
|
function removeNamespacedHandlers(element, events, typeEvent, namespace) {
|
|
const storeElementEvent = events[typeEvent] || {}
|
|
|
|
Object.keys(storeElementEvent).forEach(handlerKey => {
|
|
if (handlerKey.includes(namespace)) {
|
|
const event = storeElementEvent[handlerKey]
|
|
|
|
removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
|
|
}
|
|
})
|
|
}
|
|
|
|
function getTypeEvent(event) {
|
|
// allow to get the native events from namespaced events ('click.bs.button' --> 'click')
|
|
event = event.replace(stripNameRegex, '')
|
|
return customEvents[event] || event
|
|
}
|
|
|
|
const EventHandler = {
|
|
on(element, event, handler, delegationFn) {
|
|
addHandler(element, event, handler, delegationFn, false)
|
|
},
|
|
|
|
one(element, event, handler, delegationFn) {
|
|
addHandler(element, event, handler, delegationFn, true)
|
|
},
|
|
|
|
off(element, originalTypeEvent, handler, delegationFn) {
|
|
if (typeof originalTypeEvent !== 'string' || !element) {
|
|
return
|
|
}
|
|
|
|
const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn)
|
|
const inNamespace = typeEvent !== originalTypeEvent
|
|
const events = getEvent(element)
|
|
const isNamespace = originalTypeEvent.startsWith('.')
|
|
|
|
if (typeof originalHandler !== 'undefined') {
|
|
// Simplest case: handler is passed, remove that listener ONLY.
|
|
if (!events || !events[typeEvent]) {
|
|
return
|
|
}
|
|
|
|
removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null)
|
|
return
|
|
}
|
|
|
|
if (isNamespace) {
|
|
Object.keys(events).forEach(elementEvent => {
|
|
removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))
|
|
})
|
|
}
|
|
|
|
const storeElementEvent = events[typeEvent] || {}
|
|
Object.keys(storeElementEvent).forEach(keyHandlers => {
|
|
const handlerKey = keyHandlers.replace(stripUidRegex, '')
|
|
|
|
if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
|
|
const event = storeElementEvent[keyHandlers]
|
|
|
|
removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector)
|
|
}
|
|
})
|
|
},
|
|
|
|
trigger(element, event, args) {
|
|
if (typeof event !== 'string' || !element) {
|
|
return null
|
|
}
|
|
|
|
const $ = getjQuery()
|
|
const typeEvent = getTypeEvent(event)
|
|
const inNamespace = event !== typeEvent
|
|
const isNative = nativeEvents.has(typeEvent)
|
|
|
|
let jQueryEvent
|
|
let bubbles = true
|
|
let nativeDispatch = true
|
|
let defaultPrevented = false
|
|
let evt = null
|
|
|
|
if (inNamespace && $) {
|
|
jQueryEvent = $.Event(event, args)
|
|
|
|
$(element).trigger(jQueryEvent)
|
|
bubbles = !jQueryEvent.isPropagationStopped()
|
|
nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()
|
|
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') {
|
|
Object.keys(args).forEach(key => {
|
|
Object.defineProperty(evt, key, {
|
|
get() {
|
|
return args[key]
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
if (defaultPrevented) {
|
|
evt.preventDefault()
|
|
}
|
|
|
|
if (nativeDispatch) {
|
|
element.dispatchEvent(evt)
|
|
}
|
|
|
|
if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') {
|
|
jQueryEvent.preventDefault()
|
|
}
|
|
|
|
return evt
|
|
}
|
|
}
|
|
return EventHandler;
|
|
}(util.index.getjQuery);
|
|
|
|
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): util/backdrop.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
util.Backdrop = function(EventHandler, execute, executeAfterTransition, getElement, reflow, typeCheckConfig){
|
|
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)
|
|
}
|
|
}
|
|
|
|
return Backdrop
|
|
}(
|
|
dom.EventHandler,
|
|
util.index.execute,
|
|
util.index.executeAfterTransition,
|
|
util.index.getElement,
|
|
util.index.reflow,
|
|
util.index.typeCheckConfig
|
|
);
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): util/sanitizer.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
util.sanitizer = function(){
|
|
|
|
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
|
|
}
|
|
|
|
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: []
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return {
|
|
DefaultAllowlist,
|
|
sanitizeHtml
|
|
};
|
|
}();
|
|
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): util/scrollBar.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
util.ScrollBarHelper = function(SelectorEngine, Manipulator, isElement){
|
|
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)
|
|
this._element.style.setProperty('--scroll-bar-size', `${width}px`);
|
|
// 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')
|
|
this._element.style.setProperty('--scroll-bar-size', '0');
|
|
}
|
|
|
|
_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
|
|
}
|
|
}
|
|
return ScrollBarHelper;
|
|
}(
|
|
dom.SelectorEngine,
|
|
dom.Manipulator,
|
|
util.index.isElement
|
|
);
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v5.0.2): base-component.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
|
|
bsCore.BaseComponent = function (Data, executeAfterTransition, getElement, EventHandler) {
|
|
|
|
const VERSION = '5.0.2'
|
|
|
|
class BaseComponent {
|
|
constructor(element) {
|
|
element = getElement(element)
|
|
|
|
if (!element) {
|
|
return
|
|
}
|
|
|
|
this._element = element
|
|
Data.set(this._element, this.constructor.DATA_KEY, this)
|
|
}
|
|
|
|
dispose() {
|
|
Data.remove(this._element, this.constructor.DATA_KEY)
|
|
EventHandler.off(this._element, this.constructor.EVENT_KEY)
|
|
|
|
Object.getOwnPropertyNames(this).forEach(propertyName => {
|
|
this[propertyName] = null
|
|
})
|
|
}
|
|
|
|
_queueCallback(callback, element, isAnimated = true) {
|
|
executeAfterTransition(callback, element, isAnimated)
|
|
}
|
|
|
|
/** Static */
|
|
|
|
static getInstance(element) {
|
|
return Data.get(element, this.DATA_KEY)
|
|
}
|
|
|
|
static getOrCreateInstance(element, config = {}) {
|
|
return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)
|
|
}
|
|
|
|
static get VERSION() {
|
|
return VERSION
|
|
}
|
|
|
|
static get NAME() {
|
|
throw new Error('You have to implement the static method "NAME", for each component!')
|
|
}
|
|
|
|
static get DATA_KEY() {
|
|
return `bs.${this.NAME}`
|
|
}
|
|
|
|
static get EVENT_KEY() {
|
|
return `.${this.DATA_KEY}`
|
|
}
|
|
}
|
|
|
|
return BaseComponent;
|
|
}(
|
|
dom.Data,
|
|
util.index.executeAfterTransition,
|
|
util.index.getElement,
|
|
dom.EventHandler
|
|
);
|
|
|
|
bsCore.util = util;
|
|
bsCore.dom = dom;
|
|
|
|
|
|
module.exports = bsCore;
|
|
|
|
return module.exports;
|
|
}); |