diff options
Diffstat (limited to 'vendor/twbs/bootstrap/js/src')
24 files changed, 3075 insertions, 2275 deletions
diff --git a/vendor/twbs/bootstrap/js/src/alert.js b/vendor/twbs/bootstrap/js/src/alert.js index afd7736c7..192bea89f 100644 --- a/vendor/twbs/bootstrap/js/src/alert.js +++ b/vendor/twbs/bootstrap/js/src/alert.js @@ -1,12 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): alert.js + * Bootstrap (v5.1.3): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { defineJQueryPlugin } from './util/index' +import EventHandler from './dom/event-handler' +import BaseComponent from './base-component' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -15,19 +17,11 @@ import Util from './util' */ const NAME = 'alert' -const VERSION = '4.6.0' const DATA_KEY = 'bs.alert' const EVENT_KEY = `.${DATA_KEY}` -const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] - -const SELECTOR_DISMISS = '[data-dismiss="alert"]' const EVENT_CLOSE = `close${EVENT_KEY}` const EVENT_CLOSED = `closed${EVENT_KEY}` -const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` - -const CLASS_NAME_ALERT = 'alert' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' @@ -37,111 +31,51 @@ const CLASS_NAME_SHOW = 'show' * ------------------------------------------------------------------------ */ -class Alert { - constructor(element) { - this._element = element - } - +class Alert extends BaseComponent { // Getters - static get VERSION() { - return VERSION + static get NAME() { + return NAME } // Public - close(element) { - let rootElement = this._element - if (element) { - rootElement = this._getRootElement(element) - } - - const customEvent = this._triggerCloseEvent(rootElement) + close() { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE) - if (customEvent.isDefaultPrevented()) { + if (closeEvent.defaultPrevented) { return } - this._removeElement(rootElement) - } + this._element.classList.remove(CLASS_NAME_SHOW) - dispose() { - $.removeData(this._element, DATA_KEY) - this._element = null + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE) + this._queueCallback(() => this._destroyElement(), this._element, isAnimated) } // Private - - _getRootElement(element) { - const selector = Util.getSelectorFromElement(element) - let parent = false - - if (selector) { - parent = document.querySelector(selector) - } - - if (!parent) { - parent = $(element).closest(`.${CLASS_NAME_ALERT}`)[0] - } - - return parent - } - - _triggerCloseEvent(element) { - const closeEvent = $.Event(EVENT_CLOSE) - - $(element).trigger(closeEvent) - return closeEvent - } - - _removeElement(element) { - $(element).removeClass(CLASS_NAME_SHOW) - - if (!$(element).hasClass(CLASS_NAME_FADE)) { - this._destroyElement(element) - return - } - - const transitionDuration = Util.getTransitionDurationFromElement(element) - - $(element) - .one(Util.TRANSITION_END, event => this._destroyElement(element, event)) - .emulateTransitionEnd(transitionDuration) - } - - _destroyElement(element) { - $(element) - .detach() - .trigger(EVENT_CLOSED) - .remove() + _destroyElement() { + this._element.remove() + EventHandler.trigger(this._element, EVENT_CLOSED) + this.dispose() } // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - const $element = $(this) - let data = $element.data(DATA_KEY) - - if (!data) { - data = new Alert(this) - $element.data(DATA_KEY, data) - } + const data = Alert.getOrCreateInstance(this) - if (config === 'close') { - data[config](this) + if (typeof config !== 'string') { + return } - }) - } - static _handleDismiss(alertInstance) { - return function (event) { - if (event) { - event.preventDefault() + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) } - alertInstance.close(this) - } + data[config](this) + }) } } @@ -151,23 +85,15 @@ class Alert { * ------------------------------------------------------------------------ */ -$(document).on( - EVENT_CLICK_DATA_API, - SELECTOR_DISMISS, - Alert._handleDismiss(new Alert()) -) +enableDismissTrigger(Alert, 'close') /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Alert to jQuery only if jQuery is present */ -$.fn[NAME] = Alert._jQueryInterface -$.fn[NAME].Constructor = Alert -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Alert._jQueryInterface -} +defineJQueryPlugin(Alert) export default Alert diff --git a/vendor/twbs/bootstrap/js/src/base-component.js b/vendor/twbs/bootstrap/js/src/base-component.js new file mode 100644 index 000000000..28cf877fb --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/base-component.js @@ -0,0 +1,75 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): base-component.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import Data from './dom/data' +import { + executeAfterTransition, + getElement +} from './util/index' +import EventHandler from './dom/event-handler' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const VERSION = '5.1.3' + +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(getElement(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}` + } +} + +export default BaseComponent diff --git a/vendor/twbs/bootstrap/js/src/button.js b/vendor/twbs/bootstrap/js/src/button.js index 316387e8e..82d1b87db 100644 --- a/vendor/twbs/bootstrap/js/src/button.js +++ b/vendor/twbs/bootstrap/js/src/button.js @@ -1,11 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): button.js + * Bootstrap (v5.1.3): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' +import { defineJQueryPlugin } from './util/index' +import EventHandler from './dom/event-handler' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -14,28 +16,15 @@ import $ from 'jquery' */ const NAME = 'button' -const VERSION = '4.6.0' const DATA_KEY = 'bs.button' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] const CLASS_NAME_ACTIVE = 'active' -const CLASS_NAME_BUTTON = 'btn' -const CLASS_NAME_FOCUS = 'focus' -const SELECTOR_DATA_TOGGLE_CARROT = '[data-toggle^="button"]' -const SELECTOR_DATA_TOGGLES = '[data-toggle="buttons"]' -const SELECTOR_DATA_TOGGLE = '[data-toggle="button"]' -const SELECTOR_DATA_TOGGLES_BUTTONS = '[data-toggle="buttons"] .btn' -const SELECTOR_INPUT = 'input:not([type="hidden"])' -const SELECTOR_ACTIVE = '.active' -const SELECTOR_BUTTON = '.btn' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="button"]' const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` -const EVENT_FOCUS_BLUR_DATA_API = `focus${EVENT_KEY}${DATA_API_KEY} ` + - `blur${EVENT_KEY}${DATA_API_KEY}` -const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` /** * ------------------------------------------------------------------------ @@ -43,86 +32,25 @@ const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` * ------------------------------------------------------------------------ */ -class Button { - constructor(element) { - this._element = element - this.shouldAvoidTriggerChange = false - } - +class Button extends BaseComponent { // Getters - static get VERSION() { - return VERSION + static get NAME() { + return NAME } // Public toggle() { - let triggerChangeEvent = true - let addAriaPressed = true - const rootElement = $(this._element).closest(SELECTOR_DATA_TOGGLES)[0] - - if (rootElement) { - const input = this._element.querySelector(SELECTOR_INPUT) - - if (input) { - if (input.type === 'radio') { - if (input.checked && this._element.classList.contains(CLASS_NAME_ACTIVE)) { - triggerChangeEvent = false - } else { - const activeElement = rootElement.querySelector(SELECTOR_ACTIVE) - - if (activeElement) { - $(activeElement).removeClass(CLASS_NAME_ACTIVE) - } - } - } - - if (triggerChangeEvent) { - // if it's not a radio button or checkbox don't add a pointless/invalid checked property to the input - if (input.type === 'checkbox' || input.type === 'radio') { - input.checked = !this._element.classList.contains(CLASS_NAME_ACTIVE) - } - - if (!this.shouldAvoidTriggerChange) { - $(input).trigger('change') - } - } - - input.focus() - addAriaPressed = false - } - } - - if (!(this._element.hasAttribute('disabled') || this._element.classList.contains('disabled'))) { - if (addAriaPressed) { - this._element.setAttribute('aria-pressed', !this._element.classList.contains(CLASS_NAME_ACTIVE)) - } - - if (triggerChangeEvent) { - $(this._element).toggleClass(CLASS_NAME_ACTIVE) - } - } - } - - dispose() { - $.removeData(this._element, DATA_KEY) - this._element = null + // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method + this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE)) } // Static - static _jQueryInterface(config, avoidTriggerChange) { + static jQueryInterface(config) { return this.each(function () { - const $element = $(this) - let data = $element.data(DATA_KEY) - - if (!data) { - data = new Button(this) - $element.data(DATA_KEY, data) - } - - data.shouldAvoidTriggerChange = avoidTriggerChange + const data = Button.getOrCreateInstance(this) if (config === 'toggle') { data[config]() @@ -137,73 +65,22 @@ class Button { * ------------------------------------------------------------------------ */ -$(document) - .on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, event => { - let button = event.target - const initialButton = button - - if (!$(button).hasClass(CLASS_NAME_BUTTON)) { - button = $(button).closest(SELECTOR_BUTTON)[0] - } - - if (!button || button.hasAttribute('disabled') || button.classList.contains('disabled')) { - event.preventDefault() // work around Firefox bug #1540995 - } else { - const inputBtn = button.querySelector(SELECTOR_INPUT) - - if (inputBtn && (inputBtn.hasAttribute('disabled') || inputBtn.classList.contains('disabled'))) { - event.preventDefault() // work around Firefox bug #1540995 - return - } +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { + event.preventDefault() - if (initialButton.tagName === 'INPUT' || button.tagName !== 'LABEL') { - Button._jQueryInterface.call($(button), 'toggle', initialButton.tagName === 'INPUT') - } - } - }) - .on(EVENT_FOCUS_BLUR_DATA_API, SELECTOR_DATA_TOGGLE_CARROT, event => { - const button = $(event.target).closest(SELECTOR_BUTTON)[0] - $(button).toggleClass(CLASS_NAME_FOCUS, /^focus(in)?$/.test(event.type)) - }) - -$(window).on(EVENT_LOAD_DATA_API, () => { - // ensure correct active class is set to match the controls' actual values/states - - // find all checkboxes/readio buttons inside data-toggle groups - let buttons = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLES_BUTTONS)) - for (let i = 0, len = buttons.length; i < len; i++) { - const button = buttons[i] - const input = button.querySelector(SELECTOR_INPUT) - if (input.checked || input.hasAttribute('checked')) { - button.classList.add(CLASS_NAME_ACTIVE) - } else { - button.classList.remove(CLASS_NAME_ACTIVE) - } - } + const button = event.target.closest(SELECTOR_DATA_TOGGLE) + const data = Button.getOrCreateInstance(button) - // find all button toggles - buttons = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE)) - for (let i = 0, len = buttons.length; i < len; i++) { - const button = buttons[i] - if (button.getAttribute('aria-pressed') === 'true') { - button.classList.add(CLASS_NAME_ACTIVE) - } else { - button.classList.remove(CLASS_NAME_ACTIVE) - } - } + data.toggle() }) /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Button to jQuery only if jQuery is present */ -$.fn[NAME] = Button._jQueryInterface -$.fn[NAME].Constructor = Button -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Button._jQueryInterface -} +defineJQueryPlugin(Button) export default Button diff --git a/vendor/twbs/bootstrap/js/src/carousel.js b/vendor/twbs/bootstrap/js/src/carousel.js index b63d406bd..2cb71dcaa 100644 --- a/vendor/twbs/bootstrap/js/src/carousel.js +++ b/vendor/twbs/bootstrap/js/src/carousel.js @@ -1,12 +1,24 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): carousel.js + * Bootstrap (v5.1.3): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { + defineJQueryPlugin, + getElementFromSelector, + isRTL, + isVisible, + getNextActiveElement, + reflow, + triggerTransitionEnd, + typeCheckConfig +} from './util/index' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -15,13 +27,12 @@ import Util from './util' */ const NAME = 'carousel' -const VERSION = '4.6.0' const DATA_KEY = 'bs.carousel' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] -const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key -const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key + +const ARROW_LEFT_KEY = 'ArrowLeft' +const ARROW_RIGHT_KEY = 'ArrowRight' const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch const SWIPE_THRESHOLD = 40 @@ -43,11 +54,16 @@ const DefaultType = { touch: 'boolean' } -const DIRECTION_NEXT = 'next' -const DIRECTION_PREV = 'prev' +const ORDER_NEXT = 'next' +const ORDER_PREV = 'prev' const DIRECTION_LEFT = 'left' const DIRECTION_RIGHT = 'right' +const KEY_TO_DIRECTION = { + [ARROW_LEFT_KEY]: DIRECTION_RIGHT, + [ARROW_RIGHT_KEY]: DIRECTION_LEFT +} + const EVENT_SLIDE = `slide${EVENT_KEY}` const EVENT_SLID = `slid${EVENT_KEY}` const EVENT_KEYDOWN = `keydown${EVENT_KEY}` @@ -65,8 +81,8 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_CAROUSEL = 'carousel' const CLASS_NAME_ACTIVE = 'active' const CLASS_NAME_SLIDE = 'slide' -const CLASS_NAME_RIGHT = 'carousel-item-right' -const CLASS_NAME_LEFT = 'carousel-item-left' +const CLASS_NAME_END = 'carousel-item-end' +const CLASS_NAME_START = 'carousel-item-start' const CLASS_NAME_NEXT = 'carousel-item-next' const CLASS_NAME_PREV = 'carousel-item-prev' const CLASS_NAME_POINTER_EVENT = 'pointer-event' @@ -77,21 +93,22 @@ const SELECTOR_ITEM = '.carousel-item' const SELECTOR_ITEM_IMG = '.carousel-item img' const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev' const SELECTOR_INDICATORS = '.carousel-indicators' -const SELECTOR_DATA_SLIDE = '[data-slide], [data-slide-to]' -const SELECTOR_DATA_RIDE = '[data-ride="carousel"]' +const SELECTOR_INDICATOR = '[data-bs-target]' +const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]' +const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]' -const PointerType = { - TOUCH: 'touch', - PEN: 'pen' -} +const POINTER_TYPE_TOUCH = 'touch' +const POINTER_TYPE_PEN = 'pen' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ -class Carousel { +class Carousel extends BaseComponent { constructor(element, config) { + super(element) + this._items = null this._interval = null this._activeElement = null @@ -102,46 +119,39 @@ class Carousel { this.touchDeltaX = 0 this._config = this._getConfig(config) - this._element = element - this._indicatorsElement = this._element.querySelector(SELECTOR_INDICATORS) + this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element) this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0 - this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent) + this._pointerEvent = Boolean(window.PointerEvent) this._addEventListeners() } // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } + static get NAME() { + return NAME + } + // Public next() { - if (!this._isSliding) { - this._slide(DIRECTION_NEXT) - } + this._slide(ORDER_NEXT) } nextWhenVisible() { - const $element = $(this._element) // Don't call next when the page isn't visible // or the carousel or its parent isn't visible - if (!document.hidden && - ($element.is(':visible') && $element.css('visibility') !== 'hidden')) { + if (!document.hidden && isVisible(this._element)) { this.next() } } prev() { - if (!this._isSliding) { - this._slide(DIRECTION_PREV) - } + this._slide(ORDER_PREV) } pause(event) { @@ -149,8 +159,8 @@ class Carousel { this._isPaused = true } - if (this._element.querySelector(SELECTOR_NEXT_PREV)) { - Util.triggerTransitionEnd(this._element) + if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) { + triggerTransitionEnd(this._element) this.cycle(true) } @@ -168,7 +178,7 @@ class Carousel { this._interval = null } - if (this._config.interval && !this._isPaused) { + if (this._config && this._config.interval && !this._isPaused) { this._updateInterval() this._interval = setInterval( @@ -179,8 +189,7 @@ class Carousel { } to(index) { - this._activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM) - + this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) const activeIndex = this._getItemIndex(this._activeElement) if (index > this._items.length - 1 || index < 0) { @@ -188,7 +197,7 @@ class Carousel { } if (this._isSliding) { - $(this._element).one(EVENT_SLID, () => this.to(index)) + EventHandler.one(this._element, EVENT_SLID, () => this.to(index)) return } @@ -198,25 +207,11 @@ class Carousel { return } - const direction = index > activeIndex ? - DIRECTION_NEXT : - DIRECTION_PREV - - this._slide(direction, this._items[index]) - } + const order = index > activeIndex ? + ORDER_NEXT : + ORDER_PREV - dispose() { - $(this._element).off(EVENT_KEY) - $.removeData(this._element, DATA_KEY) - - this._items = null - this._config = null - this._element = null - this._interval = null - this._isPaused = null - this._isSliding = null - this._activeElement = null - this._indicatorsElement = null + this._slide(order, this._items[index]) } // Private @@ -224,9 +219,10 @@ class Carousel { _getConfig(config) { config = { ...Default, - ...config + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}) } - Util.typeCheckConfig(NAME, config, DefaultType) + typeCheckConfig(NAME, config, DefaultType) return config } @@ -241,58 +237,52 @@ class Carousel { this.touchDeltaX = 0 - // swipe left - if (direction > 0) { - this.prev() + if (!direction) { + return } - // swipe right - if (direction < 0) { - this.next() - } + this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT) } _addEventListeners() { if (this._config.keyboard) { - $(this._element).on(EVENT_KEYDOWN, event => this._keydown(event)) + EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)) } if (this._config.pause === 'hover') { - $(this._element) - .on(EVENT_MOUSEENTER, event => this.pause(event)) - .on(EVENT_MOUSELEAVE, event => this.cycle(event)) + EventHandler.on(this._element, EVENT_MOUSEENTER, event => this.pause(event)) + EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event)) } - if (this._config.touch) { + if (this._config.touch && this._touchSupported) { this._addTouchEventListeners() } } _addTouchEventListeners() { - if (!this._touchSupported) { - return + const hasPointerPenTouch = event => { + return this._pointerEvent && + (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH) } const start = event => { - if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { - this.touchStartX = event.originalEvent.clientX + if (hasPointerPenTouch(event)) { + this.touchStartX = event.clientX } else if (!this._pointerEvent) { - this.touchStartX = event.originalEvent.touches[0].clientX + this.touchStartX = event.touches[0].clientX } } const move = event => { // ensure swiping with one touch and not pinching - if (event.originalEvent.touches && event.originalEvent.touches.length > 1) { - this.touchDeltaX = 0 - } else { - this.touchDeltaX = event.originalEvent.touches[0].clientX - this.touchStartX - } + this.touchDeltaX = event.touches && event.touches.length > 1 ? + 0 : + event.touches[0].clientX - this.touchStartX } const end = event => { - if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { - this.touchDeltaX = event.originalEvent.clientX - this.touchStartX + if (hasPointerPenTouch(event)) { + this.touchDeltaX = event.clientX - this.touchStartX } this._handleSwipe() @@ -314,18 +304,19 @@ class Carousel { } } - $(this._element.querySelectorAll(SELECTOR_ITEM_IMG)) - .on(EVENT_DRAG_START, e => e.preventDefault()) + SelectorEngine.find(SELECTOR_ITEM_IMG, this._element).forEach(itemImg => { + EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault()) + }) if (this._pointerEvent) { - $(this._element).on(EVENT_POINTERDOWN, event => start(event)) - $(this._element).on(EVENT_POINTERUP, event => end(event)) + EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event)) + EventHandler.on(this._element, EVENT_POINTERUP, event => end(event)) this._element.classList.add(CLASS_NAME_POINTER_EVENT) } else { - $(this._element).on(EVENT_TOUCHSTART, event => start(event)) - $(this._element).on(EVENT_TOUCHMOVE, event => move(event)) - $(this._element).on(EVENT_TOUCHEND, event => end(event)) + EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event)) + EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event)) + EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event)) } } @@ -334,83 +325,65 @@ class Carousel { return } - switch (event.which) { - case ARROW_LEFT_KEYCODE: - event.preventDefault() - this.prev() - break - case ARROW_RIGHT_KEYCODE: - event.preventDefault() - this.next() - break - default: + const direction = KEY_TO_DIRECTION[event.key] + if (direction) { + event.preventDefault() + this._slide(direction) } } _getItemIndex(element) { this._items = element && element.parentNode ? - [].slice.call(element.parentNode.querySelectorAll(SELECTOR_ITEM)) : + SelectorEngine.find(SELECTOR_ITEM, element.parentNode) : [] + return this._items.indexOf(element) } - _getItemByDirection(direction, activeElement) { - const isNextDirection = direction === DIRECTION_NEXT - const isPrevDirection = direction === DIRECTION_PREV - const activeIndex = this._getItemIndex(activeElement) - const lastItemIndex = this._items.length - 1 - const isGoingToWrap = isPrevDirection && activeIndex === 0 || - isNextDirection && activeIndex === lastItemIndex - - if (isGoingToWrap && !this._config.wrap) { - return activeElement - } - - const delta = direction === DIRECTION_PREV ? -1 : 1 - const itemIndex = (activeIndex + delta) % this._items.length - - return itemIndex === -1 ? - this._items[this._items.length - 1] : this._items[itemIndex] + _getItemByOrder(order, activeElement) { + const isNext = order === ORDER_NEXT + return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap) } _triggerSlideEvent(relatedTarget, eventDirectionName) { const targetIndex = this._getItemIndex(relatedTarget) - const fromIndex = this._getItemIndex(this._element.querySelector(SELECTOR_ACTIVE_ITEM)) - const slideEvent = $.Event(EVENT_SLIDE, { + const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)) + + return EventHandler.trigger(this._element, EVENT_SLIDE, { relatedTarget, direction: eventDirectionName, from: fromIndex, to: targetIndex }) - - $(this._element).trigger(slideEvent) - - return slideEvent } _setActiveIndicatorElement(element) { if (this._indicatorsElement) { - const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(SELECTOR_ACTIVE)) - $(indicators).removeClass(CLASS_NAME_ACTIVE) + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement) - const nextIndicator = this._indicatorsElement.children[ - this._getItemIndex(element) - ] + activeIndicator.classList.remove(CLASS_NAME_ACTIVE) + activeIndicator.removeAttribute('aria-current') - if (nextIndicator) { - $(nextIndicator).addClass(CLASS_NAME_ACTIVE) + const indicators = SelectorEngine.find(SELECTOR_INDICATOR, this._indicatorsElement) + + for (let i = 0; i < indicators.length; i++) { + if (Number.parseInt(indicators[i].getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(element)) { + indicators[i].classList.add(CLASS_NAME_ACTIVE) + indicators[i].setAttribute('aria-current', 'true') + break + } } } } _updateInterval() { - const element = this._activeElement || this._element.querySelector(SELECTOR_ACTIVE_ITEM) + const element = this._activeElement || SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) if (!element) { return } - const elementInterval = parseInt(element.getAttribute('data-interval'), 10) + const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10) if (elementInterval) { this._config.defaultInterval = this._config.defaultInterval || this._config.interval @@ -420,35 +393,31 @@ class Carousel { } } - _slide(direction, element) { - const activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM) + _slide(directionOrOrder, element) { + const order = this._directionToOrder(directionOrOrder) + const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) const activeElementIndex = this._getItemIndex(activeElement) - const nextElement = element || activeElement && - this._getItemByDirection(direction, activeElement) + const nextElement = element || this._getItemByOrder(order, activeElement) + const nextElementIndex = this._getItemIndex(nextElement) const isCycling = Boolean(this._interval) - let directionalClassName - let orderClassName - let eventDirectionName + const isNext = order === ORDER_NEXT + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV + const eventDirectionName = this._orderToDirection(order) - if (direction === DIRECTION_NEXT) { - directionalClassName = CLASS_NAME_LEFT - orderClassName = CLASS_NAME_NEXT - eventDirectionName = DIRECTION_LEFT - } else { - directionalClassName = CLASS_NAME_RIGHT - orderClassName = CLASS_NAME_PREV - eventDirectionName = DIRECTION_RIGHT + if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) { + this._isSliding = false + return } - if (nextElement && $(nextElement).hasClass(CLASS_NAME_ACTIVE)) { - this._isSliding = false + if (this._isSliding) { return } const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName) - if (slideEvent.isDefaultPrevented()) { + if (slideEvent.defaultPrevented) { return } @@ -466,42 +435,41 @@ class Carousel { this._setActiveIndicatorElement(nextElement) this._activeElement = nextElement - const slidEvent = $.Event(EVENT_SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }) + const triggerSlidEvent = () => { + EventHandler.trigger(this._element, EVENT_SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex + }) + } - if ($(this._element).hasClass(CLASS_NAME_SLIDE)) { - $(nextElement).addClass(orderClassName) + if (this._element.classList.contains(CLASS_NAME_SLIDE)) { + nextElement.classList.add(orderClassName) - Util.reflow(nextElement) + reflow(nextElement) - $(activeElement).addClass(directionalClassName) - $(nextElement).addClass(directionalClassName) + activeElement.classList.add(directionalClassName) + nextElement.classList.add(directionalClassName) - const transitionDuration = Util.getTransitionDurationFromElement(activeElement) + const completeCallBack = () => { + nextElement.classList.remove(directionalClassName, orderClassName) + nextElement.classList.add(CLASS_NAME_ACTIVE) - $(activeElement) - .one(Util.TRANSITION_END, () => { - $(nextElement) - .removeClass(`${directionalClassName} ${orderClassName}`) - .addClass(CLASS_NAME_ACTIVE) + activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName) - $(activeElement).removeClass(`${CLASS_NAME_ACTIVE} ${orderClassName} ${directionalClassName}`) + this._isSliding = false - this._isSliding = false + setTimeout(triggerSlidEvent, 0) + } - setTimeout(() => $(this._element).trigger(slidEvent), 0) - }) - .emulateTransitionEnd(transitionDuration) + this._queueCallback(completeCallBack, activeElement, true) } else { - $(activeElement).removeClass(CLASS_NAME_ACTIVE) - $(nextElement).addClass(CLASS_NAME_ACTIVE) + activeElement.classList.remove(CLASS_NAME_ACTIVE) + nextElement.classList.add(CLASS_NAME_ACTIVE) this._isSliding = false - $(this._element).trigger(slidEvent) + triggerSlidEvent() } if (isCycling) { @@ -509,72 +477,86 @@ class Carousel { } } + _directionToOrder(direction) { + if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) { + return direction + } + + if (isRTL()) { + return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT + } + + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV + } + + _orderToDirection(order) { + if (![ORDER_NEXT, ORDER_PREV].includes(order)) { + return order + } + + if (isRTL()) { + return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT + } + + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT + } + // Static - static _jQueryInterface(config) { - return this.each(function () { - let data = $(this).data(DATA_KEY) - let _config = { - ...Default, - ...$(this).data() - } + static carouselInterface(element, config) { + const data = Carousel.getOrCreateInstance(element, config) - if (typeof config === 'object') { - _config = { - ..._config, - ...config - } + let { _config } = data + if (typeof config === 'object') { + _config = { + ..._config, + ...config } + } - const action = typeof config === 'string' ? config : _config.slide + const action = typeof config === 'string' ? config : _config.slide - if (!data) { - data = new Carousel(this, _config) - $(this).data(DATA_KEY, data) + if (typeof config === 'number') { + data.to(config) + } else if (typeof action === 'string') { + if (typeof data[action] === 'undefined') { + throw new TypeError(`No method named "${action}"`) } - if (typeof config === 'number') { - data.to(config) - } else if (typeof action === 'string') { - if (typeof data[action] === 'undefined') { - throw new TypeError(`No method named "${action}"`) - } + data[action]() + } else if (_config.interval && _config.ride) { + data.pause() + data.cycle() + } + } - data[action]() - } else if (_config.interval && _config.ride) { - data.pause() - data.cycle() - } + static jQueryInterface(config) { + return this.each(function () { + Carousel.carouselInterface(this, config) }) } - static _dataApiClickHandler(event) { - const selector = Util.getSelectorFromElement(this) - - if (!selector) { - return - } - - const target = $(selector)[0] + static dataApiClickHandler(event) { + const target = getElementFromSelector(this) - if (!target || !$(target).hasClass(CLASS_NAME_CAROUSEL)) { + if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { return } const config = { - ...$(target).data(), - ...$(this).data() + ...Manipulator.getDataAttributes(target), + ...Manipulator.getDataAttributes(this) } - const slideIndex = this.getAttribute('data-slide-to') + const slideIndex = this.getAttribute('data-bs-slide-to') if (slideIndex) { config.interval = false } - Carousel._jQueryInterface.call($(target), config) + Carousel.carouselInterface(target, config) if (slideIndex) { - $(target).data(DATA_KEY).to(slideIndex) + Carousel.getInstance(target).to(slideIndex) } event.preventDefault() @@ -587,13 +569,13 @@ class Carousel { * ------------------------------------------------------------------------ */ -$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel._dataApiClickHandler) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler) + +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE) -$(window).on(EVENT_LOAD_DATA_API, () => { - const carousels = [].slice.call(document.querySelectorAll(SELECTOR_DATA_RIDE)) for (let i = 0, len = carousels.length; i < len; i++) { - const $carousel = $(carousels[i]) - Carousel._jQueryInterface.call($carousel, $carousel.data()) + Carousel.carouselInterface(carousels[i], Carousel.getInstance(carousels[i])) } }) @@ -601,13 +583,9 @@ $(window).on(EVENT_LOAD_DATA_API, () => { * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Carousel to jQuery only if jQuery is present */ -$.fn[NAME] = Carousel._jQueryInterface -$.fn[NAME].Constructor = Carousel -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Carousel._jQueryInterface -} +defineJQueryPlugin(Carousel) export default Carousel diff --git a/vendor/twbs/bootstrap/js/src/collapse.js b/vendor/twbs/bootstrap/js/src/collapse.js index af3163be9..33d5674eb 100644 --- a/vendor/twbs/bootstrap/js/src/collapse.js +++ b/vendor/twbs/bootstrap/js/src/collapse.js @@ -1,12 +1,23 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): collapse.js + * Bootstrap (v5.1.3): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { + defineJQueryPlugin, + getElement, + getSelectorFromElement, + getElementFromSelector, + reflow, + typeCheckConfig +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -15,20 +26,18 @@ import Util from './util' */ const NAME = 'collapse' -const VERSION = '4.6.0' const DATA_KEY = 'bs.collapse' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { toggle: true, - parent: '' + parent: null } const DefaultType = { toggle: 'boolean', - parent: '(string|element)' + parent: '(null|element)' } const EVENT_SHOW = `show${EVENT_KEY}` @@ -41,12 +50,14 @@ const CLASS_NAME_SHOW = 'show' const CLASS_NAME_COLLAPSE = 'collapse' const CLASS_NAME_COLLAPSING = 'collapsing' const CLASS_NAME_COLLAPSED = 'collapsed' +const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}` +const CLASS_NAME_HORIZONTAL = 'collapse-horizontal' -const DIMENSION_WIDTH = 'width' -const DIMENSION_HEIGHT = 'height' +const WIDTH = 'width' +const HEIGHT = 'height' -const SELECTOR_ACTIVES = '.show, .collapsing' -const SELECTOR_DATA_TOGGLE = '[data-toggle="collapse"]' +const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="collapse"]' /** * ------------------------------------------------------------------------ @@ -54,33 +65,32 @@ const SELECTOR_DATA_TOGGLE = '[data-toggle="collapse"]' * ------------------------------------------------------------------------ */ -class Collapse { +class Collapse extends BaseComponent { constructor(element, config) { + super(element) + this._isTransitioning = false - this._element = element this._config = this._getConfig(config) - this._triggerArray = [].slice.call(document.querySelectorAll( - `[data-toggle="collapse"][href="#${element.id}"],` + - `[data-toggle="collapse"][data-target="#${element.id}"]` - )) + this._triggerArray = [] + + const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) - const toggleList = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE)) for (let i = 0, len = toggleList.length; i < len; i++) { const elem = toggleList[i] - const selector = Util.getSelectorFromElement(elem) - const filterElement = [].slice.call(document.querySelectorAll(selector)) - .filter(foundElem => foundElem === element) + const selector = getSelectorFromElement(elem) + const filterElement = SelectorEngine.find(selector) + .filter(foundElem => foundElem === this._element) - if (selector !== null && filterElement.length > 0) { + if (selector !== null && filterElement.length) { this._selector = selector this._triggerArray.push(elem) } } - this._parent = this._config.parent ? this._getParent() : null + this._initializeChildren() if (!this._config.parent) { - this._addAriaAndCollapsedClass(this._element, this._triggerArray) + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()) } if (this._config.toggle) { @@ -90,18 +100,18 @@ class Collapse { // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } + static get NAME() { + return NAME + } + // Public toggle() { - if ($(this._element).hasClass(CLASS_NAME_SHOW)) { + if (this._isShown()) { this.hide() } else { this.show() @@ -109,97 +119,78 @@ class Collapse { } show() { - if (this._isTransitioning || - $(this._element).hasClass(CLASS_NAME_SHOW)) { + if (this._isTransitioning || this._isShown()) { return } - let actives + let actives = [] let activesData - if (this._parent) { - actives = [].slice.call(this._parent.querySelectorAll(SELECTOR_ACTIVES)) - .filter(elem => { - if (typeof this._config.parent === 'string') { - return elem.getAttribute('data-parent') === this._config.parent - } - - return elem.classList.contains(CLASS_NAME_COLLAPSE) - }) - - if (actives.length === 0) { - actives = null - } + if (this._config.parent) { + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent) + actives = SelectorEngine.find(SELECTOR_ACTIVES, this._config.parent).filter(elem => !children.includes(elem)) // remove children if greater depth } - if (actives) { - activesData = $(actives).not(this._selector).data(DATA_KEY) + const container = SelectorEngine.findOne(this._selector) + if (actives.length) { + const tempActiveData = actives.find(elem => container !== elem) + activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null + if (activesData && activesData._isTransitioning) { return } } - const startEvent = $.Event(EVENT_SHOW) - $(this._element).trigger(startEvent) - if (startEvent.isDefaultPrevented()) { + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW) + if (startEvent.defaultPrevented) { return } - if (actives) { - Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide') + actives.forEach(elemActive => { + if (container !== elemActive) { + Collapse.getOrCreateInstance(elemActive, { toggle: false }).hide() + } + if (!activesData) { - $(actives).data(DATA_KEY, null) + Data.set(elemActive, DATA_KEY, null) } - } + }) const dimension = this._getDimension() - $(this._element) - .removeClass(CLASS_NAME_COLLAPSE) - .addClass(CLASS_NAME_COLLAPSING) + this._element.classList.remove(CLASS_NAME_COLLAPSE) + this._element.classList.add(CLASS_NAME_COLLAPSING) this._element.style[dimension] = 0 - if (this._triggerArray.length) { - $(this._triggerArray) - .removeClass(CLASS_NAME_COLLAPSED) - .attr('aria-expanded', true) - } - - this.setTransitioning(true) + this._addAriaAndCollapsedClass(this._triggerArray, true) + this._isTransitioning = true const complete = () => { - $(this._element) - .removeClass(CLASS_NAME_COLLAPSING) - .addClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`) + this._isTransitioning = false - this._element.style[dimension] = '' + this._element.classList.remove(CLASS_NAME_COLLAPSING) + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) - this.setTransitioning(false) + this._element.style[dimension] = '' - $(this._element).trigger(EVENT_SHOWN) + EventHandler.trigger(this._element, EVENT_SHOWN) } const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) const scrollSize = `scroll${capitalizedDimension}` - const transitionDuration = Util.getTransitionDurationFromElement(this._element) - - $(this._element) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) + this._queueCallback(complete, this._element, true) this._element.style[dimension] = `${this._element[scrollSize]}px` } hide() { - if (this._isTransitioning || - !$(this._element).hasClass(CLASS_NAME_SHOW)) { + if (this._isTransitioning || !this._isShown()) { return } - const startEvent = $.Event(EVENT_HIDE) - $(this._element).trigger(startEvent) - if (startEvent.isDefaultPrevented()) { + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE) + if (startEvent.defaultPrevented) { return } @@ -207,58 +198,37 @@ class Collapse { this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px` - Util.reflow(this._element) + reflow(this._element) - $(this._element) - .addClass(CLASS_NAME_COLLAPSING) - .removeClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`) + this._element.classList.add(CLASS_NAME_COLLAPSING) + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) const triggerArrayLength = this._triggerArray.length - if (triggerArrayLength > 0) { - for (let i = 0; i < triggerArrayLength; i++) { - const trigger = this._triggerArray[i] - const selector = Util.getSelectorFromElement(trigger) - - if (selector !== null) { - const $elem = $([].slice.call(document.querySelectorAll(selector))) - if (!$elem.hasClass(CLASS_NAME_SHOW)) { - $(trigger).addClass(CLASS_NAME_COLLAPSED) - .attr('aria-expanded', false) - } - } + for (let i = 0; i < triggerArrayLength; i++) { + const trigger = this._triggerArray[i] + const elem = getElementFromSelector(trigger) + + if (elem && !this._isShown(elem)) { + this._addAriaAndCollapsedClass([trigger], false) } } - this.setTransitioning(true) + this._isTransitioning = true const complete = () => { - this.setTransitioning(false) - $(this._element) - .removeClass(CLASS_NAME_COLLAPSING) - .addClass(CLASS_NAME_COLLAPSE) - .trigger(EVENT_HIDDEN) + this._isTransitioning = false + this._element.classList.remove(CLASS_NAME_COLLAPSING) + this._element.classList.add(CLASS_NAME_COLLAPSE) + EventHandler.trigger(this._element, EVENT_HIDDEN) } this._element.style[dimension] = '' - const transitionDuration = Util.getTransitionDurationFromElement(this._element) - $(this._element) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) + this._queueCallback(complete, this._element, true) } - setTransitioning(isTransitioning) { - this._isTransitioning = isTransitioning - } - - dispose() { - $.removeData(this._element, DATA_KEY) - - this._config = null - this._parent = null - this._element = null - this._triggerArray = null - this._isTransitioning = null + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW) } // Private @@ -266,80 +236,61 @@ class Collapse { _getConfig(config) { config = { ...Default, + ...Manipulator.getDataAttributes(this._element), ...config } config.toggle = Boolean(config.toggle) // Coerce string values - Util.typeCheckConfig(NAME, config, DefaultType) + config.parent = getElement(config.parent) + typeCheckConfig(NAME, config, DefaultType) return config } _getDimension() { - const hasWidth = $(this._element).hasClass(DIMENSION_WIDTH) - return hasWidth ? DIMENSION_WIDTH : DIMENSION_HEIGHT + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT } - _getParent() { - let parent - - if (Util.isElement(this._config.parent)) { - parent = this._config.parent - - // It's a jQuery object - if (typeof this._config.parent.jquery !== 'undefined') { - parent = this._config.parent[0] - } - } else { - parent = document.querySelector(this._config.parent) + _initializeChildren() { + if (!this._config.parent) { + return } - const selector = `[data-toggle="collapse"][data-parent="${this._config.parent}"]` - const children = [].slice.call(parent.querySelectorAll(selector)) - - $(children).each((i, element) => { - this._addAriaAndCollapsedClass( - Collapse._getTargetFromElement(element), - [element] - ) - }) + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent) + SelectorEngine.find(SELECTOR_DATA_TOGGLE, this._config.parent).filter(elem => !children.includes(elem)) + .forEach(element => { + const selected = getElementFromSelector(element) - return parent + if (selected) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)) + } + }) } - _addAriaAndCollapsedClass(element, triggerArray) { - const isOpen = $(element).hasClass(CLASS_NAME_SHOW) - - if (triggerArray.length) { - $(triggerArray) - .toggleClass(CLASS_NAME_COLLAPSED, !isOpen) - .attr('aria-expanded', isOpen) + _addAriaAndCollapsedClass(triggerArray, isOpen) { + if (!triggerArray.length) { + return } - } - // Static + triggerArray.forEach(elem => { + if (isOpen) { + elem.classList.remove(CLASS_NAME_COLLAPSED) + } else { + elem.classList.add(CLASS_NAME_COLLAPSED) + } - static _getTargetFromElement(element) { - const selector = Util.getSelectorFromElement(element) - return selector ? document.querySelector(selector) : null + elem.setAttribute('aria-expanded', isOpen) + }) } - static _jQueryInterface(config) { - return this.each(function () { - const $element = $(this) - let data = $element.data(DATA_KEY) - const _config = { - ...Default, - ...$element.data(), - ...(typeof config === 'object' && config ? config : {}) - } + // Static - if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) { + static jQueryInterface(config) { + return this.each(function () { + const _config = {} + if (typeof config === 'string' && /show|hide/.test(config)) { _config.toggle = false } - if (!data) { - data = new Collapse(this, _config) - $element.data(DATA_KEY, data) - } + const data = Collapse.getOrCreateInstance(this, _config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -358,21 +309,17 @@ class Collapse { * ------------------------------------------------------------------------ */ -$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { // preventDefault only for <a> elements (which change the URL) not inside the collapsible element - if (event.currentTarget.tagName === 'A') { + if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) { event.preventDefault() } - const $trigger = $(this) - const selector = Util.getSelectorFromElement(this) - const selectors = [].slice.call(document.querySelectorAll(selector)) + const selector = getSelectorFromElement(this) + const selectorElements = SelectorEngine.find(selector) - $(selectors).each(function () { - const $target = $(this) - const data = $target.data(DATA_KEY) - const config = data ? 'toggle' : $trigger.data() - Collapse._jQueryInterface.call($target, config) + selectorElements.forEach(element => { + Collapse.getOrCreateInstance(element, { toggle: false }).toggle() }) }) @@ -380,13 +327,9 @@ $(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Collapse to jQuery only if jQuery is present */ -$.fn[NAME] = Collapse._jQueryInterface -$.fn[NAME].Constructor = Collapse -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Collapse._jQueryInterface -} +defineJQueryPlugin(Collapse) export default Collapse diff --git a/vendor/twbs/bootstrap/js/src/dom/data.js b/vendor/twbs/bootstrap/js/src/dom/data.js new file mode 100644 index 000000000..c702fc82c --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/dom/data.js @@ -0,0 +1,57 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/data.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const elementMap = new Map() + +export default { + 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) + } + } +} diff --git a/vendor/twbs/bootstrap/js/src/dom/event-handler.js b/vendor/twbs/bootstrap/js/src/dom/event-handler.js new file mode 100644 index 000000000..e085ea138 --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/dom/event-handler.js @@ -0,0 +1,348 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/event-handler.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { getjQuery } from '../util/index' + +/** + * ------------------------------------------------------------------------ + * 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) { + 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 + } +} + +export default EventHandler diff --git a/vendor/twbs/bootstrap/js/src/dom/manipulator.js b/vendor/twbs/bootstrap/js/src/dom/manipulator.js new file mode 100644 index 000000000..04982d63e --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/dom/manipulator.js @@ -0,0 +1,80 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/manipulator.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +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 + window.pageYOffset, + left: rect.left + window.pageXOffset + } + }, + + position(element) { + return { + top: element.offsetTop, + left: element.offsetLeft + } + } +} + +export default Manipulator diff --git a/vendor/twbs/bootstrap/js/src/dom/selector-engine.js b/vendor/twbs/bootstrap/js/src/dom/selector-engine.js new file mode 100644 index 000000000..54f270f4c --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/dom/selector-engine.js @@ -0,0 +1,92 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): dom/selector-engine.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +import { isDisabled, isVisible } from '../util/index' + +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 [] + }, + + focusableChildren(element) { + const focusables = [ + 'a', + 'button', + 'input', + 'textarea', + 'select', + 'details', + '[tabindex]', + '[contenteditable="true"]' + ].map(selector => `${selector}:not([tabindex^="-"])`).join(', ') + + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) + } +} + +export default SelectorEngine diff --git a/vendor/twbs/bootstrap/js/src/dropdown.js b/vendor/twbs/bootstrap/js/src/dropdown.js index 76dda8f57..6fafdb116 100644 --- a/vendor/twbs/bootstrap/js/src/dropdown.js +++ b/vendor/twbs/bootstrap/js/src/dropdown.js @@ -1,13 +1,28 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): dropdown.js + * Bootstrap (v5.1.3): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Popper from 'popper.js' -import Util from './util' +import * as Popper from '@popperjs/core' + +import { + defineJQueryPlugin, + getElement, + getElementFromSelector, + getNextActiveElement, + isDisabled, + isElement, + isRTL, + isVisible, + noop, + typeCheckConfig +} from './util/index' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -16,65 +31,61 @@ import Util from './util' */ const NAME = 'dropdown' -const VERSION = '4.6.0' const DATA_KEY = 'bs.dropdown' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] -const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key -const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key -const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key -const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key -const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key -const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse) -const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`) + +const ESCAPE_KEY = 'Escape' +const SPACE_KEY = 'Space' +const TAB_KEY = 'Tab' +const ARROW_UP_KEY = 'ArrowUp' +const ARROW_DOWN_KEY = 'ArrowDown' +const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button + +const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`) const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` -const EVENT_CLICK = `click${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` -const CLASS_NAME_DISABLED = 'disabled' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_DROPUP = 'dropup' -const CLASS_NAME_DROPRIGHT = 'dropright' -const CLASS_NAME_DROPLEFT = 'dropleft' -const CLASS_NAME_MENURIGHT = 'dropdown-menu-right' -const CLASS_NAME_POSITION_STATIC = 'position-static' +const CLASS_NAME_DROPEND = 'dropend' +const CLASS_NAME_DROPSTART = 'dropstart' +const CLASS_NAME_NAVBAR = 'navbar' -const SELECTOR_DATA_TOGGLE = '[data-toggle="dropdown"]' -const SELECTOR_FORM_CHILD = '.dropdown form' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' const SELECTOR_MENU = '.dropdown-menu' const SELECTOR_NAVBAR_NAV = '.navbar-nav' const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' -const PLACEMENT_TOP = 'top-start' -const PLACEMENT_TOPEND = 'top-end' -const PLACEMENT_BOTTOM = 'bottom-start' -const PLACEMENT_BOTTOMEND = 'bottom-end' -const PLACEMENT_RIGHT = 'right-start' -const PLACEMENT_LEFT = 'left-start' +const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start' +const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end' +const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start' +const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end' +const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start' +const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' const Default = { - offset: 0, - flip: true, - boundary: 'scrollParent', + offset: [0, 2], + boundary: 'clippingParents', reference: 'toggle', display: 'dynamic', - popperConfig: null + popperConfig: null, + autoClose: true } const DefaultType = { - offset: '(number|string|function)', - flip: 'boolean', + offset: '(array|string|function)', boundary: '(string|element)', - reference: '(string|element)', + reference: '(string|element|object)', display: 'string', - popperConfig: '(null|object)' + popperConfig: '(null|object|function)', + autoClose: '(boolean|string)' } /** @@ -83,23 +94,18 @@ const DefaultType = { * ------------------------------------------------------------------------ */ -class Dropdown { +class Dropdown extends BaseComponent { constructor(element, config) { - this._element = element + super(element) + this._popper = null this._config = this._getConfig(config) this._menu = this._getMenuElement() this._inNavbar = this._detectNavbar() - - this._addEventListeners() } // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } @@ -108,72 +114,37 @@ class Dropdown { return DefaultType } + static get NAME() { + return NAME + } + // Public toggle() { - if (this._element.disabled || $(this._element).hasClass(CLASS_NAME_DISABLED)) { - return - } - - const isActive = $(this._menu).hasClass(CLASS_NAME_SHOW) - - Dropdown._clearMenus() - - if (isActive) { - return - } - - this.show(true) + return this._isShown() ? this.hide() : this.show() } - show(usePopper = false) { - if (this._element.disabled || $(this._element).hasClass(CLASS_NAME_DISABLED) || $(this._menu).hasClass(CLASS_NAME_SHOW)) { + show() { + if (isDisabled(this._element) || this._isShown(this._menu)) { return } const relatedTarget = { relatedTarget: this._element } - const showEvent = $.Event(EVENT_SHOW, relatedTarget) - const parent = Dropdown._getParentFromElement(this._element) - $(parent).trigger(showEvent) + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget) - if (showEvent.isDefaultPrevented()) { + if (showEvent.defaultPrevented) { return } + const parent = Dropdown.getParentFromElement(this._element) // Totally disable Popper for Dropdowns in Navbar - if (!this._inNavbar && usePopper) { - /** - * Check for Popper dependency - * Popper - https://popper.js.org - */ - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)') - } - - let referenceElement = this._element - - if (this._config.reference === 'parent') { - referenceElement = parent - } else if (Util.isElement(this._config.reference)) { - referenceElement = this._config.reference - - // Check if it's jQuery element - if (typeof this._config.reference.jquery !== 'undefined') { - referenceElement = this._config.reference[0] - } - } - - // If boundary is not `scrollParent`, then set position to `static` - // to allow the menu to "escape" the scroll parent's boundaries - // https://github.com/twbs/bootstrap/issues/24251 - if (this._config.boundary !== 'scrollParent') { - $(parent).addClass(CLASS_NAME_POSITION_STATIC) - } - - this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig()) + if (this._inNavbar) { + Manipulator.setDataAttribute(this._menu, 'popper', 'none') + } else { + this._createPopper(parent) } // If this is a touch-enabled device we add extra @@ -181,266 +152,275 @@ class Dropdown { // only needed because of broken event delegation on iOS // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement && - $(parent).closest(SELECTOR_NAVBAR_NAV).length === 0) { - $(document.body).children().on('mouseover', null, $.noop) + !parent.closest(SELECTOR_NAVBAR_NAV)) { + [].concat(...document.body.children) + .forEach(elem => EventHandler.on(elem, 'mouseover', noop)) } this._element.focus() this._element.setAttribute('aria-expanded', true) - $(this._menu).toggleClass(CLASS_NAME_SHOW) - $(parent) - .toggleClass(CLASS_NAME_SHOW) - .trigger($.Event(EVENT_SHOWN, relatedTarget)) + this._menu.classList.add(CLASS_NAME_SHOW) + this._element.classList.add(CLASS_NAME_SHOW) + EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget) } hide() { - if (this._element.disabled || $(this._element).hasClass(CLASS_NAME_DISABLED) || !$(this._menu).hasClass(CLASS_NAME_SHOW)) { + if (isDisabled(this._element) || !this._isShown(this._menu)) { return } const relatedTarget = { relatedTarget: this._element } - const hideEvent = $.Event(EVENT_HIDE, relatedTarget) - const parent = Dropdown._getParentFromElement(this._element) - - $(parent).trigger(hideEvent) - - if (hideEvent.isDefaultPrevented()) { - return - } - - if (this._popper) { - this._popper.destroy() - } - $(this._menu).toggleClass(CLASS_NAME_SHOW) - $(parent) - .toggleClass(CLASS_NAME_SHOW) - .trigger($.Event(EVENT_HIDDEN, relatedTarget)) + this._completeHide(relatedTarget) } dispose() { - $.removeData(this._element, DATA_KEY) - $(this._element).off(EVENT_KEY) - this._element = null - this._menu = null - if (this._popper !== null) { + if (this._popper) { this._popper.destroy() - this._popper = null } + + super.dispose() } update() { this._inNavbar = this._detectNavbar() - if (this._popper !== null) { - this._popper.scheduleUpdate() + if (this._popper) { + this._popper.update() } } // Private - _addEventListeners() { - $(this._element).on(EVENT_CLICK, event => { - event.preventDefault() - event.stopPropagation() - this.toggle() - }) + _completeHide(relatedTarget) { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget) + if (hideEvent.defaultPrevented) { + return + } + + // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { + [].concat(...document.body.children) + .forEach(elem => EventHandler.off(elem, 'mouseover', noop)) + } + + if (this._popper) { + this._popper.destroy() + } + + this._menu.classList.remove(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOW) + this._element.setAttribute('aria-expanded', 'false') + Manipulator.removeDataAttribute(this._menu, 'popper') + EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) } _getConfig(config) { config = { ...this.constructor.Default, - ...$(this._element).data(), + ...Manipulator.getDataAttributes(this._element), ...config } - Util.typeCheckConfig( - NAME, - config, - this.constructor.DefaultType - ) + typeCheckConfig(NAME, config, this.constructor.DefaultType) + + if (typeof config.reference === 'object' && !isElement(config.reference) && + typeof config.reference.getBoundingClientRect !== 'function' + ) { + // Popper virtual elements require a getBoundingClientRect method + throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`) + } return config } - _getMenuElement() { - if (!this._menu) { - const parent = Dropdown._getParentFromElement(this._element) + _createPopper(parent) { + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)') + } - if (parent) { - this._menu = parent.querySelector(SELECTOR_MENU) - } + let referenceElement = this._element + + if (this._config.reference === 'parent') { + referenceElement = parent + } else if (isElement(this._config.reference)) { + referenceElement = getElement(this._config.reference) + } else if (typeof this._config.reference === 'object') { + referenceElement = this._config.reference } - return this._menu + const popperConfig = this._getPopperConfig() + const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false) + + this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) + + if (isDisplayStatic) { + Manipulator.setDataAttribute(this._menu, 'popper', 'static') + } + } + + _isShown(element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW) + } + + _getMenuElement() { + return SelectorEngine.next(this._element, SELECTOR_MENU)[0] } _getPlacement() { - const $parentDropdown = $(this._element.parentNode) - let placement = PLACEMENT_BOTTOM - - // Handle dropup - if ($parentDropdown.hasClass(CLASS_NAME_DROPUP)) { - placement = $(this._menu).hasClass(CLASS_NAME_MENURIGHT) ? - PLACEMENT_TOPEND : - PLACEMENT_TOP - } else if ($parentDropdown.hasClass(CLASS_NAME_DROPRIGHT)) { - placement = PLACEMENT_RIGHT - } else if ($parentDropdown.hasClass(CLASS_NAME_DROPLEFT)) { - placement = PLACEMENT_LEFT - } else if ($(this._menu).hasClass(CLASS_NAME_MENURIGHT)) { - placement = PLACEMENT_BOTTOMEND - } - - return placement + const parentDropdown = this._element.parentNode + + if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { + return PLACEMENT_RIGHT + } + + if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { + return PLACEMENT_LEFT + } + + // We need to trim the value because custom properties can also include spaces + const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end' + + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { + return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP + } + + return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM } _detectNavbar() { - return $(this._element).closest('.navbar').length > 0 + return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null } _getOffset() { - const offset = {} + const { offset } = this._config - if (typeof this._config.offset === 'function') { - offset.fn = data => { - data.offsets = { - ...data.offsets, - ...(this._config.offset(data.offsets, this._element) || {}) - } + if (typeof offset === 'string') { + return offset.split(',').map(val => Number.parseInt(val, 10)) + } - return data - } - } else { - offset.offset = this._config.offset + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element) } return offset } _getPopperConfig() { - const popperConfig = { + const defaultBsPopperConfig = { placement: this._getPlacement(), - modifiers: { - offset: this._getOffset(), - flip: { - enabled: this._config.flip - }, - preventOverflow: { - boundariesElement: this._config.boundary + modifiers: [{ + name: 'preventOverflow', + options: { + boundary: this._config.boundary } - } + }, + { + name: 'offset', + options: { + offset: this._getOffset() + } + }] } // Disable Popper if we have a static display if (this._config.display === 'static') { - popperConfig.modifiers.applyStyle = { + defaultBsPopperConfig.modifiers = [{ + name: 'applyStyles', enabled: false - } + }] } return { - ...popperConfig, - ...this._config.popperConfig + ...defaultBsPopperConfig, + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) + } + } + + _selectMenuItem({ key, target }) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible) + + if (!items.length) { + return } + + // if target isn't included in items (e.g. when expanding the dropdown) + // allow cycling to get the last item in case key equals ARROW_UP_KEY + getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus() } // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - let data = $(this).data(DATA_KEY) - const _config = typeof config === 'object' ? config : null + const data = Dropdown.getOrCreateInstance(this, config) - if (!data) { - data = new Dropdown(this, _config) - $(this).data(DATA_KEY, data) + if (typeof config !== 'string') { + return } - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config]() }) } - static _clearMenus(event) { - if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || - event.type === 'keyup' && event.which !== TAB_KEYCODE)) { + static clearMenus(event) { + if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) { return } - const toggles = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE)) + const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE) for (let i = 0, len = toggles.length; i < len; i++) { - const parent = Dropdown._getParentFromElement(toggles[i]) - const context = $(toggles[i]).data(DATA_KEY) - const relatedTarget = { - relatedTarget: toggles[i] - } - - if (event && event.type === 'click') { - relatedTarget.clickEvent = event - } - - if (!context) { + const context = Dropdown.getInstance(toggles[i]) + if (!context || context._config.autoClose === false) { continue } - const dropdownMenu = context._menu - if (!$(parent).hasClass(CLASS_NAME_SHOW)) { + if (!context._isShown()) { continue } - if (event && (event.type === 'click' && - /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && - $.contains(parent, event.target)) { - continue - } - - const hideEvent = $.Event(EVENT_HIDE, relatedTarget) - $(parent).trigger(hideEvent) - if (hideEvent.isDefaultPrevented()) { - continue + const relatedTarget = { + relatedTarget: context._element } - // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - if ('ontouchstart' in document.documentElement) { - $(document.body).children().off('mouseover', null, $.noop) - } + if (event) { + const composedPath = event.composedPath() + const isMenuTarget = composedPath.includes(context._menu) + if ( + composedPath.includes(context._element) || + (context._config.autoClose === 'inside' && !isMenuTarget) || + (context._config.autoClose === 'outside' && isMenuTarget) + ) { + continue + } - toggles[i].setAttribute('aria-expanded', 'false') + // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) { + continue + } - if (context._popper) { - context._popper.destroy() + if (event.type === 'click') { + relatedTarget.clickEvent = event + } } - $(dropdownMenu).removeClass(CLASS_NAME_SHOW) - $(parent) - .removeClass(CLASS_NAME_SHOW) - .trigger($.Event(EVENT_HIDDEN, relatedTarget)) + context._completeHide(relatedTarget) } } - static _getParentFromElement(element) { - let parent - const selector = Util.getSelectorFromElement(element) - - if (selector) { - parent = document.querySelector(selector) - } - - return parent || element.parentNode + static getParentFromElement(element) { + return getElementFromSelector(element) || element.parentNode } - // eslint-disable-next-line complexity - static _dataApiKeydownHandler(event) { + static dataApiKeydownHandler(event) { // If not input/textarea: // - And not a key in REGEXP_KEYDOWN => not a dropdown command // If input/textarea: @@ -449,57 +429,46 @@ class Dropdown { // - If key is not up or down => not a dropdown command // - If trigger inside the menu => not a dropdown command if (/input|textarea/i.test(event.target.tagName) ? - event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && - (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || - $(event.target).closest(SELECTOR_MENU).length) : !REGEXP_KEYDOWN.test(event.which)) { - return - } - - if (this.disabled || $(this).hasClass(CLASS_NAME_DISABLED)) { + event.key === SPACE_KEY || (event.key !== ESCAPE_KEY && + ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) || + event.target.closest(SELECTOR_MENU))) : + !REGEXP_KEYDOWN.test(event.key)) { return } - const parent = Dropdown._getParentFromElement(this) - const isActive = $(parent).hasClass(CLASS_NAME_SHOW) + const isActive = this.classList.contains(CLASS_NAME_SHOW) - if (!isActive && event.which === ESCAPE_KEYCODE) { + if (!isActive && event.key === ESCAPE_KEY) { return } event.preventDefault() event.stopPropagation() - if (!isActive || (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { - if (event.which === ESCAPE_KEYCODE) { - $(parent.querySelector(SELECTOR_DATA_TOGGLE)).trigger('focus') - } - - $(this).trigger('click') + if (isDisabled(this)) { return } - const items = [].slice.call(parent.querySelectorAll(SELECTOR_VISIBLE_ITEMS)) - .filter(item => $(item).is(':visible')) + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] + const instance = Dropdown.getOrCreateInstance(getToggleButton) - if (items.length === 0) { + if (event.key === ESCAPE_KEY) { + instance.hide() return } - let index = items.indexOf(event.target) - - if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up - index-- - } + if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { + if (!isActive) { + instance.show() + } - if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down - index++ + instance._selectMenuItem(event) + return } - if (index < 0) { - index = 0 + if (!isActive || event.key === SPACE_KEY) { + Dropdown.clearMenus() } - - items[index].focus() } } @@ -509,30 +478,22 @@ class Dropdown { * ------------------------------------------------------------------------ */ -$(document) - .on(EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown._dataApiKeydownHandler) - .on(EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown._dataApiKeydownHandler) - .on(`${EVENT_CLICK_DATA_API} ${EVENT_KEYUP_DATA_API}`, Dropdown._clearMenus) - .on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { - event.preventDefault() - event.stopPropagation() - Dropdown._jQueryInterface.call($(this), 'toggle') - }) - .on(EVENT_CLICK_DATA_API, SELECTOR_FORM_CHILD, e => { - e.stopPropagation() - }) +EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler) +EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler) +EventHandler.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus) +EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus) +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + event.preventDefault() + Dropdown.getOrCreateInstance(this).toggle() +}) /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Dropdown to jQuery only if jQuery is present */ -$.fn[NAME] = Dropdown._jQueryInterface -$.fn[NAME].Constructor = Dropdown -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Dropdown._jQueryInterface -} +defineJQueryPlugin(Dropdown) export default Dropdown diff --git a/vendor/twbs/bootstrap/js/src/modal.js b/vendor/twbs/bootstrap/js/src/modal.js index 2e3017024..00df5c482 100644 --- a/vendor/twbs/bootstrap/js/src/modal.js +++ b/vendor/twbs/bootstrap/js/src/modal.js @@ -1,12 +1,26 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): modal.js + * Bootstrap (v5.1.3): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { + defineJQueryPlugin, + getElementFromSelector, + isRTL, + isVisible, + reflow, + typeCheckConfig +} from './util/index' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' +import ScrollBarHelper from './util/scrollbar' +import BaseComponent from './base-component' +import Backdrop from './util/backdrop' +import FocusTrap from './util/focustrap' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -15,25 +29,21 @@ import Util from './util' */ const NAME = 'modal' -const VERSION = '4.6.0' const DATA_KEY = 'bs.modal' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] -const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key +const ESCAPE_KEY = 'Escape' const Default = { backdrop: true, keyboard: true, - focus: true, - show: true + focus: true } const DefaultType = { backdrop: '(boolean|string)', keyboard: 'boolean', - focus: 'boolean', - show: 'boolean' + focus: 'boolean' } const EVENT_HIDE = `hide${EVENT_KEY}` @@ -41,7 +51,6 @@ const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` -const EVENT_FOCUSIN = `focusin${EVENT_KEY}` const EVENT_RESIZE = `resize${EVENT_KEY}` const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` @@ -49,20 +58,15 @@ const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}` const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` -const CLASS_NAME_SCROLLABLE = 'modal-dialog-scrollable' -const CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure' -const CLASS_NAME_BACKDROP = 'modal-backdrop' const CLASS_NAME_OPEN = 'modal-open' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_STATIC = 'modal-static' +const OPEN_SELECTOR = '.modal.show' const SELECTOR_DIALOG = '.modal-dialog' const SELECTOR_MODAL_BODY = '.modal-body' -const SELECTOR_DATA_TOGGLE = '[data-toggle="modal"]' -const SELECTOR_DATA_DISMISS = '[data-dismiss="modal"]' -const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' -const SELECTOR_STICKY_CONTENT = '.sticky-top' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]' /** * ------------------------------------------------------------------------ @@ -70,29 +74,30 @@ const SELECTOR_STICKY_CONTENT = '.sticky-top' * ------------------------------------------------------------------------ */ -class Modal { +class Modal extends BaseComponent { constructor(element, config) { + super(element) + this._config = this._getConfig(config) - this._element = element - this._dialog = element.querySelector(SELECTOR_DIALOG) - this._backdrop = null + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) + this._backdrop = this._initializeBackDrop() + this._focustrap = this._initializeFocusTrap() this._isShown = false - this._isBodyOverflowing = false this._ignoreBackdropClick = false this._isTransitioning = false - this._scrollbarWidth = 0 + this._scrollBar = new ScrollBarHelper() } // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } + static get NAME() { + return NAME + } + // Public toggle(relatedTarget) { @@ -104,39 +109,32 @@ class Modal { return } - if ($(this._element).hasClass(CLASS_NAME_FADE)) { - this._isTransitioning = true - } - - const showEvent = $.Event(EVENT_SHOW, { + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget }) - $(this._element).trigger(showEvent) - - if (this._isShown || showEvent.isDefaultPrevented()) { + if (showEvent.defaultPrevented) { return } this._isShown = true - this._checkScrollbar() - this._setScrollbar() + if (this._isAnimated()) { + this._isTransitioning = true + } + + this._scrollBar.hide() + + document.body.classList.add(CLASS_NAME_OPEN) this._adjustDialog() this._setEscapeEvent() this._setResizeEvent() - $(this._element).on( - EVENT_CLICK_DISMISS, - SELECTOR_DATA_DISMISS, - event => this.hide(event) - ) - - $(this._dialog).on(EVENT_MOUSEDOWN_DISMISS, () => { - $(this._element).one(EVENT_MOUSEUP_DISMISS, event => { - if ($(event.target).is(this._element)) { + EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => { + EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => { + if (event.target === this._element) { this._ignoreBackdropClick = true } }) @@ -145,73 +143,44 @@ class Modal { this._showBackdrop(() => this._showElement(relatedTarget)) } - hide(event) { - if (event) { - event.preventDefault() - } - + hide() { if (!this._isShown || this._isTransitioning) { return } - const hideEvent = $.Event(EVENT_HIDE) - - $(this._element).trigger(hideEvent) + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) - if (!this._isShown || hideEvent.isDefaultPrevented()) { + if (hideEvent.defaultPrevented) { return } this._isShown = false - const transition = $(this._element).hasClass(CLASS_NAME_FADE) + const isAnimated = this._isAnimated() - if (transition) { + if (isAnimated) { this._isTransitioning = true } this._setEscapeEvent() this._setResizeEvent() - $(document).off(EVENT_FOCUSIN) + this._focustrap.deactivate() - $(this._element).removeClass(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOW) - $(this._element).off(EVENT_CLICK_DISMISS) - $(this._dialog).off(EVENT_MOUSEDOWN_DISMISS) + EventHandler.off(this._element, EVENT_CLICK_DISMISS) + EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS) - if (transition) { - const transitionDuration = Util.getTransitionDurationFromElement(this._element) - - $(this._element) - .one(Util.TRANSITION_END, event => this._hideModal(event)) - .emulateTransitionEnd(transitionDuration) - } else { - this._hideModal() - } + this._queueCallback(() => this._hideModal(), this._element, isAnimated) } dispose() { - [window, this._element, this._dialog] - .forEach(htmlElement => $(htmlElement).off(EVENT_KEY)) - - /** - * `document` has 2 events `EVENT_FOCUSIN` and `EVENT_CLICK_DATA_API` - * Do not move `document` in `htmlElements` array - * It will remove `EVENT_CLICK_DATA_API` event that should remain - */ - $(document).off(EVENT_FOCUSIN) - - $.removeData(this._element, DATA_KEY) - - this._config = null - this._element = null - this._dialog = null - this._backdrop = null - this._isShown = null - this._isBodyOverflowing = null - this._ignoreBackdropClick = null - this._isTransitioning = null - this._scrollbarWidth = null + [window, this._dialog] + .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY)) + + this._backdrop.dispose() + this._focustrap.deactivate() + super.dispose() } handleUpdate() { @@ -220,134 +189,88 @@ class Modal { // Private + _initializeBackDrop() { + return new Backdrop({ + isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value + isAnimated: this._isAnimated() + }) + } + + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }) + } + _getConfig(config) { config = { ...Default, - ...config + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}) } - Util.typeCheckConfig(NAME, config, DefaultType) + typeCheckConfig(NAME, config, DefaultType) return config } - _triggerBackdropTransition() { - const hideEventPrevented = $.Event(EVENT_HIDE_PREVENTED) - - $(this._element).trigger(hideEventPrevented) - if (hideEventPrevented.isDefaultPrevented()) { - return - } - - const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight - - if (!isModalOverflowing) { - this._element.style.overflowY = 'hidden' - } - - this._element.classList.add(CLASS_NAME_STATIC) - - const modalTransitionDuration = Util.getTransitionDurationFromElement(this._dialog) - $(this._element).off(Util.TRANSITION_END) - - $(this._element).one(Util.TRANSITION_END, () => { - this._element.classList.remove(CLASS_NAME_STATIC) - if (!isModalOverflowing) { - $(this._element).one(Util.TRANSITION_END, () => { - this._element.style.overflowY = '' - }) - .emulateTransitionEnd(this._element, modalTransitionDuration) - } - }) - .emulateTransitionEnd(modalTransitionDuration) - this._element.focus() - } - _showElement(relatedTarget) { - const transition = $(this._element).hasClass(CLASS_NAME_FADE) - const modalBody = this._dialog ? this._dialog.querySelector(SELECTOR_MODAL_BODY) : null + const isAnimated = this._isAnimated() + const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog) - if (!this._element.parentNode || - this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { + if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { // Don't move modal's DOM position - document.body.appendChild(this._element) + document.body.append(this._element) } this._element.style.display = 'block' this._element.removeAttribute('aria-hidden') this._element.setAttribute('aria-modal', true) this._element.setAttribute('role', 'dialog') + this._element.scrollTop = 0 - if ($(this._dialog).hasClass(CLASS_NAME_SCROLLABLE) && modalBody) { + if (modalBody) { modalBody.scrollTop = 0 - } else { - this._element.scrollTop = 0 } - if (transition) { - Util.reflow(this._element) + if (isAnimated) { + reflow(this._element) } - $(this._element).addClass(CLASS_NAME_SHOW) - - if (this._config.focus) { - this._enforceFocus() - } - - const shownEvent = $.Event(EVENT_SHOWN, { - relatedTarget - }) + this._element.classList.add(CLASS_NAME_SHOW) const transitionComplete = () => { if (this._config.focus) { - this._element.focus() + this._focustrap.activate() } this._isTransitioning = false - $(this._element).trigger(shownEvent) - } - - if (transition) { - const transitionDuration = Util.getTransitionDurationFromElement(this._dialog) - - $(this._dialog) - .one(Util.TRANSITION_END, transitionComplete) - .emulateTransitionEnd(transitionDuration) - } else { - transitionComplete() + EventHandler.trigger(this._element, EVENT_SHOWN, { + relatedTarget + }) } - } - _enforceFocus() { - $(document) - .off(EVENT_FOCUSIN) // Guard against infinite focus loop - .on(EVENT_FOCUSIN, event => { - if (document !== event.target && - this._element !== event.target && - $(this._element).has(event.target).length === 0) { - this._element.focus() - } - }) + this._queueCallback(transitionComplete, this._dialog, isAnimated) } _setEscapeEvent() { if (this._isShown) { - $(this._element).on(EVENT_KEYDOWN_DISMISS, event => { - if (this._config.keyboard && event.which === ESCAPE_KEYCODE) { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if (this._config.keyboard && event.key === ESCAPE_KEY) { event.preventDefault() this.hide() - } else if (!this._config.keyboard && event.which === ESCAPE_KEYCODE) { + } else if (!this._config.keyboard && event.key === ESCAPE_KEY) { this._triggerBackdropTransition() } }) - } else if (!this._isShown) { - $(this._element).off(EVENT_KEYDOWN_DISMISS) + } else { + EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS) } } _setResizeEvent() { if (this._isShown) { - $(window).on(EVENT_RESIZE, event => this.handleUpdate(event)) + EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog()) } else { - $(window).off(EVENT_RESIZE) + EventHandler.off(window, EVENT_RESIZE) } } @@ -357,110 +280,85 @@ class Modal { this._element.removeAttribute('aria-modal') this._element.removeAttribute('role') this._isTransitioning = false - this._showBackdrop(() => { - $(document.body).removeClass(CLASS_NAME_OPEN) + this._backdrop.hide(() => { + document.body.classList.remove(CLASS_NAME_OPEN) this._resetAdjustments() - this._resetScrollbar() - $(this._element).trigger(EVENT_HIDDEN) + this._scrollBar.reset() + EventHandler.trigger(this._element, EVENT_HIDDEN) }) } - _removeBackdrop() { - if (this._backdrop) { - $(this._backdrop).remove() - this._backdrop = null - } - } - _showBackdrop(callback) { - const animate = $(this._element).hasClass(CLASS_NAME_FADE) ? - CLASS_NAME_FADE : '' - - if (this._isShown && this._config.backdrop) { - this._backdrop = document.createElement('div') - this._backdrop.className = CLASS_NAME_BACKDROP - - if (animate) { - this._backdrop.classList.add(animate) - } - - $(this._backdrop).appendTo(document.body) - - $(this._element).on(EVENT_CLICK_DISMISS, event => { - if (this._ignoreBackdropClick) { - this._ignoreBackdropClick = false - return - } - - if (event.target !== event.currentTarget) { - return - } - - if (this._config.backdrop === 'static') { - this._triggerBackdropTransition() - } else { - this.hide() - } - }) - - if (animate) { - Util.reflow(this._backdrop) + EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { + if (this._ignoreBackdropClick) { + this._ignoreBackdropClick = false + return } - $(this._backdrop).addClass(CLASS_NAME_SHOW) - - if (!callback) { + if (event.target !== event.currentTarget) { return } - if (!animate) { - callback() - return + if (this._config.backdrop === true) { + this.hide() + } else if (this._config.backdrop === 'static') { + this._triggerBackdropTransition() } + }) - const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) + this._backdrop.show(callback) + } - $(this._backdrop) - .one(Util.TRANSITION_END, callback) - .emulateTransitionEnd(backdropTransitionDuration) - } else if (!this._isShown && this._backdrop) { - $(this._backdrop).removeClass(CLASS_NAME_SHOW) + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_FADE) + } - const callbackRemove = () => { - this._removeBackdrop() - if (callback) { - callback() - } - } + _triggerBackdropTransition() { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + if (hideEvent.defaultPrevented) { + return + } - if ($(this._element).hasClass(CLASS_NAME_FADE)) { - const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) + const { classList, scrollHeight, style } = this._element + const isModalOverflowing = scrollHeight > document.documentElement.clientHeight - $(this._backdrop) - .one(Util.TRANSITION_END, callbackRemove) - .emulateTransitionEnd(backdropTransitionDuration) - } else { - callbackRemove() - } - } else if (callback) { - callback() + // return if the following background transition hasn't yet completed + if ((!isModalOverflowing && style.overflowY === 'hidden') || classList.contains(CLASS_NAME_STATIC)) { + return + } + + if (!isModalOverflowing) { + style.overflowY = 'hidden' } + + classList.add(CLASS_NAME_STATIC) + this._queueCallback(() => { + classList.remove(CLASS_NAME_STATIC) + if (!isModalOverflowing) { + this._queueCallback(() => { + style.overflowY = '' + }, this._dialog) + } + }, this._dialog) + + this._element.focus() } // ---------------------------------------------------------------------- // the following methods are used to handle overflowing modals - // todo (fat): these should probably be refactored out of modal.js // ---------------------------------------------------------------------- _adjustDialog() { const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const scrollbarWidth = this._scrollBar.getWidth() + const isBodyOverflowing = scrollbarWidth > 0 - if (!this._isBodyOverflowing && isModalOverflowing) { - this._element.style.paddingLeft = `${this._scrollbarWidth}px` + if ((!isBodyOverflowing && isModalOverflowing && !isRTL()) || (isBodyOverflowing && !isModalOverflowing && isRTL())) { + this._element.style.paddingLeft = `${scrollbarWidth}px` } - if (this._isBodyOverflowing && !isModalOverflowing) { - this._element.style.paddingRight = `${this._scrollbarWidth}px` + if ((isBodyOverflowing && !isModalOverflowing && !isRTL()) || (!isBodyOverflowing && isModalOverflowing && isRTL())) { + this._element.style.paddingRight = `${scrollbarWidth}px` } } @@ -469,106 +367,21 @@ class Modal { this._element.style.paddingRight = '' } - _checkScrollbar() { - const rect = document.body.getBoundingClientRect() - this._isBodyOverflowing = Math.round(rect.left + rect.right) < window.innerWidth - this._scrollbarWidth = this._getScrollbarWidth() - } - - _setScrollbar() { - if (this._isBodyOverflowing) { - // Note: DOMNode.style.paddingRight returns the actual value or '' if not set - // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set - const fixedContent = [].slice.call(document.querySelectorAll(SELECTOR_FIXED_CONTENT)) - const stickyContent = [].slice.call(document.querySelectorAll(SELECTOR_STICKY_CONTENT)) - - // Adjust fixed content padding - $(fixedContent).each((index, element) => { - const actualPadding = element.style.paddingRight - const calculatedPadding = $(element).css('padding-right') - $(element) - .data('padding-right', actualPadding) - .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`) - }) - - // Adjust sticky content margin - $(stickyContent).each((index, element) => { - const actualMargin = element.style.marginRight - const calculatedMargin = $(element).css('margin-right') - $(element) - .data('margin-right', actualMargin) - .css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`) - }) - - // Adjust body padding - const actualPadding = document.body.style.paddingRight - const calculatedPadding = $(document.body).css('padding-right') - $(document.body) - .data('padding-right', actualPadding) - .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`) - } - - $(document.body).addClass(CLASS_NAME_OPEN) - } - - _resetScrollbar() { - // Restore fixed content padding - const fixedContent = [].slice.call(document.querySelectorAll(SELECTOR_FIXED_CONTENT)) - $(fixedContent).each((index, element) => { - const padding = $(element).data('padding-right') - $(element).removeData('padding-right') - element.style.paddingRight = padding ? padding : '' - }) - - // Restore sticky content - const elements = [].slice.call(document.querySelectorAll(`${SELECTOR_STICKY_CONTENT}`)) - $(elements).each((index, element) => { - const margin = $(element).data('margin-right') - if (typeof margin !== 'undefined') { - $(element).css('margin-right', margin).removeData('margin-right') - } - }) - - // Restore body padding - const padding = $(document.body).data('padding-right') - $(document.body).removeData('padding-right') - document.body.style.paddingRight = padding ? padding : '' - } - - _getScrollbarWidth() { // thx d.walsh - const scrollDiv = document.createElement('div') - scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER - document.body.appendChild(scrollDiv) - const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth - document.body.removeChild(scrollDiv) - return scrollbarWidth - } - // Static - static _jQueryInterface(config, relatedTarget) { + static jQueryInterface(config, relatedTarget) { return this.each(function () { - let data = $(this).data(DATA_KEY) - const _config = { - ...Default, - ...$(this).data(), - ...(typeof config === 'object' && config ? config : {}) - } + const data = Modal.getOrCreateInstance(this, config) - if (!data) { - data = new Modal(this, _config) - $(this).data(DATA_KEY, data) + if (typeof config !== 'string') { + return } - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](relatedTarget) - } else if (_config.show) { - data.show(relatedTarget) + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config](relatedTarget) }) } } @@ -579,51 +392,46 @@ class Modal { * ------------------------------------------------------------------------ */ -$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { - let target - const selector = Util.getSelectorFromElement(this) - - if (selector) { - target = document.querySelector(selector) - } - - const config = $(target).data(DATA_KEY) ? - 'toggle' : { - ...$(target).data(), - ...$(this).data() - } +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + const target = getElementFromSelector(this) - if (this.tagName === 'A' || this.tagName === 'AREA') { + if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault() } - const $target = $(target).one(EVENT_SHOW, showEvent => { - if (showEvent.isDefaultPrevented()) { - // Only register focus restorer if modal will actually get shown + EventHandler.one(target, EVENT_SHOW, showEvent => { + if (showEvent.defaultPrevented) { + // only register focus restorer if modal will actually get shown return } - $target.one(EVENT_HIDDEN, () => { - if ($(this).is(':visible')) { + EventHandler.one(target, EVENT_HIDDEN, () => { + if (isVisible(this)) { this.focus() } }) }) - Modal._jQueryInterface.call($(target), config, this) + // avoid conflict when clicking moddal toggler while another one is open + const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) + if (allReadyOpen) { + Modal.getInstance(allReadyOpen).hide() + } + + const data = Modal.getOrCreateInstance(target) + + data.toggle(this) }) +enableDismissTrigger(Modal) + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Modal to jQuery only if jQuery is present */ -$.fn[NAME] = Modal._jQueryInterface -$.fn[NAME].Constructor = Modal -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Modal._jQueryInterface -} +defineJQueryPlugin(Modal) export default Modal diff --git a/vendor/twbs/bootstrap/js/src/offcanvas.js b/vendor/twbs/bootstrap/js/src/offcanvas.js new file mode 100644 index 000000000..66378fd23 --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/offcanvas.js @@ -0,0 +1,272 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): offcanvas.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { + defineJQueryPlugin, + getElementFromSelector, + isDisabled, + isVisible, + typeCheckConfig +} from './util/index' +import ScrollBarHelper from './util/scrollbar' +import EventHandler from './dom/event-handler' +import BaseComponent from './base-component' +import SelectorEngine from './dom/selector-engine' +import Manipulator from './dom/manipulator' +import Backdrop from './util/backdrop' +import FocusTrap from './util/focustrap' +import { enableDismissTrigger } from './util/component-functions' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'offcanvas' +const DATA_KEY = 'bs.offcanvas' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` +const ESCAPE_KEY = 'Escape' + +const Default = { + backdrop: true, + keyboard: true, + scroll: false +} + +const DefaultType = { + backdrop: 'boolean', + keyboard: 'boolean', + scroll: 'boolean' +} + +const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_BACKDROP = 'offcanvas-backdrop' +const OPEN_SELECTOR = '.offcanvas.show' + +const EVENT_SHOW = `show${EVENT_KEY}` +const EVENT_SHOWN = `shown${EVENT_KEY}` +const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDDEN = `hidden${EVENT_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` + +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Offcanvas extends BaseComponent { + constructor(element, config) { + super(element) + + this._config = this._getConfig(config) + this._isShown = false + this._backdrop = this._initializeBackDrop() + this._focustrap = this._initializeFocusTrap() + this._addEventListeners() + } + + // Getters + + static get NAME() { + return NAME + } + + static get Default() { + return Default + } + + // Public + + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + if (this._isShown) { + return + } + + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget }) + + if (showEvent.defaultPrevented) { + return + } + + this._isShown = true + this._element.style.visibility = 'visible' + + this._backdrop.show() + + if (!this._config.scroll) { + new ScrollBarHelper().hide() + } + + this._element.removeAttribute('aria-hidden') + this._element.setAttribute('aria-modal', true) + this._element.setAttribute('role', 'dialog') + this._element.classList.add(CLASS_NAME_SHOW) + + const completeCallBack = () => { + if (!this._config.scroll) { + this._focustrap.activate() + } + + EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) + } + + this._queueCallback(completeCallBack, this._element, true) + } + + hide() { + if (!this._isShown) { + return + } + + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) + + if (hideEvent.defaultPrevented) { + return + } + + this._focustrap.deactivate() + this._element.blur() + this._isShown = false + this._element.classList.remove(CLASS_NAME_SHOW) + this._backdrop.hide() + + const completeCallback = () => { + this._element.setAttribute('aria-hidden', true) + this._element.removeAttribute('aria-modal') + this._element.removeAttribute('role') + this._element.style.visibility = 'hidden' + + if (!this._config.scroll) { + new ScrollBarHelper().reset() + } + + EventHandler.trigger(this._element, EVENT_HIDDEN) + } + + this._queueCallback(completeCallback, this._element, true) + } + + dispose() { + this._backdrop.dispose() + this._focustrap.deactivate() + super.dispose() + } + + // Private + + _getConfig(config) { + config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}) + } + typeCheckConfig(NAME, config, DefaultType) + return config + } + + _initializeBackDrop() { + return new Backdrop({ + className: CLASS_NAME_BACKDROP, + isVisible: this._config.backdrop, + isAnimated: true, + rootElement: this._element.parentNode, + clickCallback: () => this.hide() + }) + } + + _initializeFocusTrap() { + return new FocusTrap({ + trapElement: this._element + }) + } + + _addEventListeners() { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if (this._config.keyboard && event.key === ESCAPE_KEY) { + this.hide() + } + }) + } + + // Static + + static jQueryInterface(config) { + return this.each(function () { + const data = Offcanvas.getOrCreateInstance(this, config) + + if (typeof config !== 'string') { + return + } + + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) + } + + data[config](this) + }) + } +} + +/** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + const target = getElementFromSelector(this) + + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } + + EventHandler.one(target, EVENT_HIDDEN, () => { + // focus on trigger when it is closed + if (isVisible(this)) { + this.focus() + } + }) + + // avoid conflict when clicking a toggler of an offcanvas, while another is open + const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) + if (allReadyOpen && allReadyOpen !== target) { + Offcanvas.getInstance(allReadyOpen).hide() + } + + const data = Offcanvas.getOrCreateInstance(target) + data.toggle(this) +}) + +EventHandler.on(window, EVENT_LOAD_DATA_API, () => + SelectorEngine.find(OPEN_SELECTOR).forEach(el => Offcanvas.getOrCreateInstance(el).show()) +) + +enableDismissTrigger(Offcanvas) +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + +defineJQueryPlugin(Offcanvas) + +export default Offcanvas diff --git a/vendor/twbs/bootstrap/js/src/popover.js b/vendor/twbs/bootstrap/js/src/popover.js index 4e2c260b9..aa9b0bc9e 100644 --- a/vendor/twbs/bootstrap/js/src/popover.js +++ b/vendor/twbs/bootstrap/js/src/popover.js @@ -1,11 +1,11 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): popover.js + * Bootstrap (v5.1.3): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' +import { defineJQueryPlugin } from './util/index' import Tooltip from './tooltip' /** @@ -15,22 +15,21 @@ import Tooltip from './tooltip' */ const NAME = 'popover' -const VERSION = '4.6.0' const DATA_KEY = 'bs.popover' const EVENT_KEY = `.${DATA_KEY}` -const JQUERY_NO_CONFLICT = $.fn[NAME] const CLASS_PREFIX = 'bs-popover' -const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g') const Default = { ...Tooltip.Default, placement: 'right', + offset: [0, 8], trigger: 'click', content: '', template: '<div class="popover" role="tooltip">' + - '<div class="arrow"></div>' + + '<div class="popover-arrow"></div>' + '<h3 class="popover-header"></h3>' + - '<div class="popover-body"></div></div>' + '<div class="popover-body"></div>' + + '</div>' } const DefaultType = { @@ -38,12 +37,6 @@ const DefaultType = { content: '(string|element|function)' } -const CLASS_NAME_FADE = 'fade' -const CLASS_NAME_SHOW = 'show' - -const SELECTOR_TITLE = '.popover-header' -const SELECTOR_CONTENT = '.popover-body' - const Event = { HIDE: `hide${EVENT_KEY}`, HIDDEN: `hidden${EVENT_KEY}`, @@ -57,6 +50,9 @@ const Event = { MOUSELEAVE: `mouseleave${EVENT_KEY}` } +const SELECTOR_TITLE = '.popover-header' +const SELECTOR_CONTENT = '.popover-body' + /** * ------------------------------------------------------------------------ * Class Definition @@ -66,10 +62,6 @@ const Event = { class Popover extends Tooltip { // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } @@ -78,18 +70,10 @@ class Popover extends Tooltip { return NAME } - static get DATA_KEY() { - return DATA_KEY - } - static get Event() { return Event } - static get EVENT_KEY() { - return EVENT_KEY - } - static get DefaultType() { return DefaultType } @@ -100,60 +84,26 @@ class Popover extends Tooltip { return this.getTitle() || this._getContent() } - addAttachmentClass(attachment) { - $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`) - } - - getTipElement() { - this.tip = this.tip || $(this.config.template)[0] - return this.tip - } - - setContent() { - const $tip = $(this.getTipElement()) - - // We use append for html objects to maintain js events - this.setElementContent($tip.find(SELECTOR_TITLE), this.getTitle()) - let content = this._getContent() - if (typeof content === 'function') { - content = content.call(this.element) - } - - this.setElementContent($tip.find(SELECTOR_CONTENT), content) - - $tip.removeClass(`${CLASS_NAME_FADE} ${CLASS_NAME_SHOW}`) + setContent(tip) { + this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TITLE) + this._sanitizeAndSetContent(tip, this._getContent(), SELECTOR_CONTENT) } // Private _getContent() { - return this.element.getAttribute('data-content') || - this.config.content + return this._resolvePossibleFunction(this._config.content) } - _cleanTipClass() { - const $tip = $(this.getTipElement()) - const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX) - if (tabClass !== null && tabClass.length > 0) { - $tip.removeClass(tabClass.join('')) - } + _getBasicClassPrefix() { + return CLASS_PREFIX } // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - let data = $(this).data(DATA_KEY) - const _config = typeof config === 'object' ? config : null - - if (!data && /dispose|hide/.test(config)) { - return - } - - if (!data) { - data = new Popover(this, _config) - $(this).data(DATA_KEY, data) - } + const data = Popover.getOrCreateInstance(this, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -170,13 +120,9 @@ class Popover extends Tooltip { * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Popover to jQuery only if jQuery is present */ -$.fn[NAME] = Popover._jQueryInterface -$.fn[NAME].Constructor = Popover -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Popover._jQueryInterface -} +defineJQueryPlugin(Popover) export default Popover diff --git a/vendor/twbs/bootstrap/js/src/scrollspy.js b/vendor/twbs/bootstrap/js/src/scrollspy.js index 351df0649..825b07fbc 100644 --- a/vendor/twbs/bootstrap/js/src/scrollspy.js +++ b/vendor/twbs/bootstrap/js/src/scrollspy.js @@ -1,12 +1,20 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): scrollspy.js + * Bootstrap (v5.1.3): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { + defineJQueryPlugin, + getElement, + getSelectorFromElement, + typeCheckConfig +} from './util/index' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -15,11 +23,9 @@ import Util from './util' */ const NAME = 'scrollspy' -const VERSION = '4.6.0' const DATA_KEY = 'bs.scrollspy' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { offset: 10, @@ -40,13 +46,13 @@ const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item' const CLASS_NAME_ACTIVE = 'active' -const SELECTOR_DATA_SPY = '[data-spy="scroll"]' +const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]' const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' const SELECTOR_NAV_LINKS = '.nav-link' const SELECTOR_NAV_ITEMS = '.nav-item' const SELECTOR_LIST_ITEMS = '.list-group-item' +const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}` const SELECTOR_DROPDOWN = '.dropdown' -const SELECTOR_DROPDOWN_ITEMS = '.dropdown-item' const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' const METHOD_OFFSET = 'offset' @@ -58,20 +64,17 @@ const METHOD_POSITION = 'position' * ------------------------------------------------------------------------ */ -class ScrollSpy { +class ScrollSpy extends BaseComponent { constructor(element, config) { - this._element = element - this._scrollElement = element.tagName === 'BODY' ? window : element + super(element) + this._scrollElement = this._element.tagName === 'BODY' ? window : this._element this._config = this._getConfig(config) - this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS},` + - `${this._config.target} ${SELECTOR_LIST_ITEMS},` + - `${this._config.target} ${SELECTOR_DROPDOWN_ITEMS}` this._offsets = [] this._targets = [] this._activeTarget = null this._scrollHeight = 0 - $(this._scrollElement).on(EVENT_SCROLL, event => this._process(event)) + EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process()) this.refresh() this._process() @@ -79,55 +82,51 @@ class ScrollSpy { // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } + static get NAME() { + return NAME + } + // Public refresh() { const autoMethod = this._scrollElement === this._scrollElement.window ? - METHOD_OFFSET : METHOD_POSITION + METHOD_OFFSET : + METHOD_POSITION const offsetMethod = this._config.method === 'auto' ? - autoMethod : this._config.method + autoMethod : + this._config.method const offsetBase = offsetMethod === METHOD_POSITION ? - this._getScrollTop() : 0 + this._getScrollTop() : + 0 this._offsets = [] this._targets = [] - this._scrollHeight = this._getScrollHeight() - const targets = [].slice.call(document.querySelectorAll(this._selector)) + const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target) - targets - .map(element => { - let target - const targetSelector = Util.getSelectorFromElement(element) + targets.map(element => { + const targetSelector = getSelectorFromElement(element) + const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null - if (targetSelector) { - target = document.querySelector(targetSelector) - } - - if (target) { - const targetBCR = target.getBoundingClientRect() - if (targetBCR.width || targetBCR.height) { - // TODO (fat): remove sketch reliance on jQuery position/offset - return [ - $(target)[offsetMethod]().top + offsetBase, - targetSelector - ] - } + if (target) { + const targetBCR = target.getBoundingClientRect() + if (targetBCR.width || targetBCR.height) { + return [ + Manipulator[offsetMethod](target).top + offsetBase, + targetSelector + ] } + } - return null - }) + return null + }) .filter(item => item) .sort((a, b) => a[0] - b[0]) .forEach(item => { @@ -137,17 +136,8 @@ class ScrollSpy { } dispose() { - $.removeData(this._element, DATA_KEY) - $(this._scrollElement).off(EVENT_KEY) - - this._element = null - this._scrollElement = null - this._config = null - this._selector = null - this._offsets = null - this._targets = null - this._activeTarget = null - this._scrollHeight = null + EventHandler.off(this._scrollElement, EVENT_KEY) + super.dispose() } // Private @@ -155,27 +145,21 @@ class ScrollSpy { _getConfig(config) { config = { ...Default, + ...Manipulator.getDataAttributes(this._element), ...(typeof config === 'object' && config ? config : {}) } - if (typeof config.target !== 'string' && Util.isElement(config.target)) { - let id = $(config.target).attr('id') - if (!id) { - id = Util.getUID(NAME) - $(config.target).attr('id', id) - } + config.target = getElement(config.target) || document.documentElement - config.target = `#${id}` - } - - Util.typeCheckConfig(NAME, config, DefaultType) + typeCheckConfig(NAME, config, DefaultType) return config } _getScrollTop() { return this._scrollElement === window ? - this._scrollElement.pageYOffset : this._scrollElement.scrollTop + this._scrollElement.pageYOffset : + this._scrollElement.scrollTop } _getScrollHeight() { @@ -187,7 +171,8 @@ class ScrollSpy { _getOffsetHeight() { return this._scrollElement === window ? - window.innerHeight : this._scrollElement.getBoundingClientRect().height + window.innerHeight : + this._scrollElement.getBoundingClientRect().height } _process() { @@ -218,8 +203,7 @@ class ScrollSpy { for (let i = this._offsets.length; i--;) { const isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && - (typeof this._offsets[i + 1] === 'undefined' || - scrollTop < this._offsets[i + 1]) + (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]) if (isActiveTarget) { this._activate(this._targets[i]) @@ -232,62 +216,58 @@ class ScrollSpy { this._clear() - const queries = this._selector - .split(',') - .map(selector => `${selector}[data-target="${target}"],${selector}[href="${target}"]`) + const queries = SELECTOR_LINK_ITEMS.split(',') + .map(selector => `${selector}[data-bs-target="${target}"],${selector}[href="${target}"]`) - const $link = $([].slice.call(document.querySelectorAll(queries.join(',')))) + const link = SelectorEngine.findOne(queries.join(','), this._config.target) - if ($link.hasClass(CLASS_NAME_DROPDOWN_ITEM)) { - $link.closest(SELECTOR_DROPDOWN) - .find(SELECTOR_DROPDOWN_TOGGLE) - .addClass(CLASS_NAME_ACTIVE) - $link.addClass(CLASS_NAME_ACTIVE) + link.classList.add(CLASS_NAME_ACTIVE) + if (link.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { + SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, link.closest(SELECTOR_DROPDOWN)) + .classList.add(CLASS_NAME_ACTIVE) } else { - // Set triggered link as active - $link.addClass(CLASS_NAME_ACTIVE) - // Set triggered links parents as active - // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor - $link.parents(SELECTOR_NAV_LIST_GROUP) - .prev(`${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`) - .addClass(CLASS_NAME_ACTIVE) - // Handle special case when .nav-link is inside .nav-item - $link.parents(SELECTOR_NAV_LIST_GROUP) - .prev(SELECTOR_NAV_ITEMS) - .children(SELECTOR_NAV_LINKS) - .addClass(CLASS_NAME_ACTIVE) + SelectorEngine.parents(link, SELECTOR_NAV_LIST_GROUP) + .forEach(listGroup => { + // Set triggered links parents as active + // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor + SelectorEngine.prev(listGroup, `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`) + .forEach(item => item.classList.add(CLASS_NAME_ACTIVE)) + + // Handle special case when .nav-link is inside .nav-item + SelectorEngine.prev(listGroup, SELECTOR_NAV_ITEMS) + .forEach(navItem => { + SelectorEngine.children(navItem, SELECTOR_NAV_LINKS) + .forEach(item => item.classList.add(CLASS_NAME_ACTIVE)) + }) + }) } - $(this._scrollElement).trigger(EVENT_ACTIVATE, { + EventHandler.trigger(this._scrollElement, EVENT_ACTIVATE, { relatedTarget: target }) } _clear() { - [].slice.call(document.querySelectorAll(this._selector)) + SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target) .filter(node => node.classList.contains(CLASS_NAME_ACTIVE)) .forEach(node => node.classList.remove(CLASS_NAME_ACTIVE)) } // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - let data = $(this).data(DATA_KEY) - const _config = typeof config === 'object' && config + const data = ScrollSpy.getOrCreateInstance(this, config) - if (!data) { - data = new ScrollSpy(this, _config) - $(this).data(DATA_KEY, data) + if (typeof config !== 'string') { + return } - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config]() }) } } @@ -298,27 +278,18 @@ class ScrollSpy { * ------------------------------------------------------------------------ */ -$(window).on(EVENT_LOAD_DATA_API, () => { - const scrollSpys = [].slice.call(document.querySelectorAll(SELECTOR_DATA_SPY)) - const scrollSpysLength = scrollSpys.length - - for (let i = scrollSpysLength; i--;) { - const $spy = $(scrollSpys[i]) - ScrollSpy._jQueryInterface.call($spy, $spy.data()) - } +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + SelectorEngine.find(SELECTOR_DATA_SPY) + .forEach(spy => new ScrollSpy(spy)) }) /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .ScrollSpy to jQuery only if jQuery is present */ -$.fn[NAME] = ScrollSpy._jQueryInterface -$.fn[NAME].Constructor = ScrollSpy -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return ScrollSpy._jQueryInterface -} +defineJQueryPlugin(ScrollSpy) export default ScrollSpy diff --git a/vendor/twbs/bootstrap/js/src/tab.js b/vendor/twbs/bootstrap/js/src/tab.js index e9a6f555f..139a16cb4 100644 --- a/vendor/twbs/bootstrap/js/src/tab.js +++ b/vendor/twbs/bootstrap/js/src/tab.js @@ -1,12 +1,19 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): tab.js + * Bootstrap (v5.1.3): tab.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { + defineJQueryPlugin, + getElementFromSelector, + isDisabled, + reflow +} from './util/index' +import EventHandler from './dom/event-handler' +import SelectorEngine from './dom/selector-engine' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -15,11 +22,9 @@ import Util from './util' */ const NAME = 'tab' -const VERSION = '4.6.0' const DATA_KEY = 'bs.tab' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` @@ -29,17 +34,16 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu' const CLASS_NAME_ACTIVE = 'active' -const CLASS_NAME_DISABLED = 'disabled' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' const SELECTOR_DROPDOWN = '.dropdown' const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' const SELECTOR_ACTIVE = '.active' -const SELECTOR_ACTIVE_UL = '> li > .active' -const SELECTOR_DATA_TOGGLE = '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]' +const SELECTOR_ACTIVE_UL = ':scope > li > .active' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' -const SELECTOR_DROPDOWN_ACTIVE_CHILD = '> .dropdown-menu .active' +const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active' /** * ------------------------------------------------------------------------ @@ -47,77 +51,55 @@ const SELECTOR_DROPDOWN_ACTIVE_CHILD = '> .dropdown-menu .active' * ------------------------------------------------------------------------ */ -class Tab { - constructor(element) { - this._element = element - } - +class Tab extends BaseComponent { // Getters - static get VERSION() { - return VERSION + static get NAME() { + return NAME } // Public show() { - if (this._element.parentNode && - this._element.parentNode.nodeType === Node.ELEMENT_NODE && - $(this._element).hasClass(CLASS_NAME_ACTIVE) || - $(this._element).hasClass(CLASS_NAME_DISABLED)) { + if ((this._element.parentNode && + this._element.parentNode.nodeType === Node.ELEMENT_NODE && + this._element.classList.contains(CLASS_NAME_ACTIVE))) { return } - let target let previous - const listElement = $(this._element).closest(SELECTOR_NAV_LIST_GROUP)[0] - const selector = Util.getSelectorFromElement(this._element) + const target = getElementFromSelector(this._element) + const listElement = this._element.closest(SELECTOR_NAV_LIST_GROUP) if (listElement) { const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? SELECTOR_ACTIVE_UL : SELECTOR_ACTIVE - previous = $.makeArray($(listElement).find(itemSelector)) + previous = SelectorEngine.find(itemSelector, listElement) previous = previous[previous.length - 1] } - const hideEvent = $.Event(EVENT_HIDE, { - relatedTarget: this._element - }) + const hideEvent = previous ? + EventHandler.trigger(previous, EVENT_HIDE, { + relatedTarget: this._element + }) : + null - const showEvent = $.Event(EVENT_SHOW, { + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget: previous }) - if (previous) { - $(previous).trigger(hideEvent) - } - - $(this._element).trigger(showEvent) - - if (showEvent.isDefaultPrevented() || - hideEvent.isDefaultPrevented()) { + if (showEvent.defaultPrevented || (hideEvent !== null && hideEvent.defaultPrevented)) { return } - if (selector) { - target = document.querySelector(selector) - } - - this._activate( - this._element, - listElement - ) + this._activate(this._element, listElement) const complete = () => { - const hiddenEvent = $.Event(EVENT_HIDDEN, { + EventHandler.trigger(previous, EVENT_HIDDEN, { relatedTarget: this._element }) - - const shownEvent = $.Event(EVENT_SHOWN, { + EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget: previous }) - - $(previous).trigger(hiddenEvent) - $(this._element).trigger(shownEvent) } if (target) { @@ -127,33 +109,21 @@ class Tab { } } - dispose() { - $.removeData(this._element, DATA_KEY) - this._element = null - } - // Private _activate(element, container, callback) { const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? - $(container).find(SELECTOR_ACTIVE_UL) : - $(container).children(SELECTOR_ACTIVE) + SelectorEngine.find(SELECTOR_ACTIVE_UL, container) : + SelectorEngine.children(container, SELECTOR_ACTIVE) const active = activeElements[0] - const isTransitioning = callback && (active && $(active).hasClass(CLASS_NAME_FADE)) - const complete = () => this._transitionComplete( - element, - active, - callback - ) + const isTransitioning = callback && (active && active.classList.contains(CLASS_NAME_FADE)) - if (active && isTransitioning) { - const transitionDuration = Util.getTransitionDurationFromElement(active) + const complete = () => this._transitionComplete(element, active, callback) - $(active) - .removeClass(CLASS_NAME_SHOW) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) + if (active && isTransitioning) { + active.classList.remove(CLASS_NAME_SHOW) + this._queueCallback(complete, element, true) } else { complete() } @@ -161,14 +131,12 @@ class Tab { _transitionComplete(element, active, callback) { if (active) { - $(active).removeClass(CLASS_NAME_ACTIVE) + active.classList.remove(CLASS_NAME_ACTIVE) - const dropdownChild = $(active.parentNode).find( - SELECTOR_DROPDOWN_ACTIVE_CHILD - )[0] + const dropdownChild = SelectorEngine.findOne(SELECTOR_DROPDOWN_ACTIVE_CHILD, active.parentNode) if (dropdownChild) { - $(dropdownChild).removeClass(CLASS_NAME_ACTIVE) + dropdownChild.classList.remove(CLASS_NAME_ACTIVE) } if (active.getAttribute('role') === 'tab') { @@ -176,24 +144,28 @@ class Tab { } } - $(element).addClass(CLASS_NAME_ACTIVE) + element.classList.add(CLASS_NAME_ACTIVE) if (element.getAttribute('role') === 'tab') { element.setAttribute('aria-selected', true) } - Util.reflow(element) + reflow(element) if (element.classList.contains(CLASS_NAME_FADE)) { element.classList.add(CLASS_NAME_SHOW) } - if (element.parentNode && $(element.parentNode).hasClass(CLASS_NAME_DROPDOWN_MENU)) { - const dropdownElement = $(element).closest(SELECTOR_DROPDOWN)[0] + let parent = element.parentNode + if (parent && parent.nodeName === 'LI') { + parent = parent.parentNode + } + + if (parent && parent.classList.contains(CLASS_NAME_DROPDOWN_MENU)) { + const dropdownElement = element.closest(SELECTOR_DROPDOWN) if (dropdownElement) { - const dropdownToggleList = [].slice.call(dropdownElement.querySelectorAll(SELECTOR_DROPDOWN_TOGGLE)) - - $(dropdownToggleList).addClass(CLASS_NAME_ACTIVE) + SelectorEngine.find(SELECTOR_DROPDOWN_TOGGLE, dropdownElement) + .forEach(dropdown => dropdown.classList.add(CLASS_NAME_ACTIVE)) } element.setAttribute('aria-expanded', true) @@ -206,15 +178,9 @@ class Tab { // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - const $this = $(this) - let data = $this.data(DATA_KEY) - - if (!data) { - data = new Tab(this) - $this.data(DATA_KEY, data) - } + const data = Tab.getOrCreateInstance(this) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -233,23 +199,26 @@ class Tab { * ------------------------------------------------------------------------ */ -$(document) - .on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault() - Tab._jQueryInterface.call($(this), 'show') - }) + } + + if (isDisabled(this)) { + return + } + + const data = Tab.getOrCreateInstance(this) + data.show() +}) /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Tab to jQuery only if jQuery is present */ -$.fn[NAME] = Tab._jQueryInterface -$.fn[NAME].Constructor = Tab -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Tab._jQueryInterface -} +defineJQueryPlugin(Tab) export default Tab diff --git a/vendor/twbs/bootstrap/js/src/toast.js b/vendor/twbs/bootstrap/js/src/toast.js index 0c2186908..780279be9 100644 --- a/vendor/twbs/bootstrap/js/src/toast.js +++ b/vendor/twbs/bootstrap/js/src/toast.js @@ -1,12 +1,19 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): toast.js + * Bootstrap (v5.1.3): toast.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import $ from 'jquery' -import Util from './util' +import { + defineJQueryPlugin, + reflow, + typeCheckConfig +} from './util/index' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import BaseComponent from './base-component' +import { enableDismissTrigger } from './util/component-functions' /** * ------------------------------------------------------------------------ @@ -15,19 +22,20 @@ import Util from './util' */ const NAME = 'toast' -const VERSION = '4.6.0' const DATA_KEY = 'bs.toast' const EVENT_KEY = `.${DATA_KEY}` -const JQUERY_NO_CONFLICT = $.fn[NAME] -const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` +const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}` +const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}` +const EVENT_FOCUSIN = `focusin${EVENT_KEY}` +const EVENT_FOCUSOUT = `focusout${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` const CLASS_NAME_FADE = 'fade' -const CLASS_NAME_HIDE = 'hide' +const CLASS_NAME_HIDE = 'hide' // @deprecated - kept here only for backwards compatibility const CLASS_NAME_SHOW = 'show' const CLASS_NAME_SHOWING = 'showing' @@ -40,31 +48,28 @@ const DefaultType = { const Default = { animation: true, autohide: true, - delay: 500 + delay: 5000 } -const SELECTOR_DATA_DISMISS = '[data-dismiss="toast"]' - /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ -class Toast { +class Toast extends BaseComponent { constructor(element, config) { - this._element = element + super(element) + this._config = this._getConfig(config) this._timeout = null + this._hasMouseInteraction = false + this._hasKeyboardInteraction = false this._setListeners() } // Getters - static get VERSION() { - return VERSION - } - static get DefaultType() { return DefaultType } @@ -73,13 +78,16 @@ class Toast { return Default } + static get NAME() { + return NAME + } + // Public show() { - const showEvent = $.Event(EVENT_SHOW) + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW) - $(this._element).trigger(showEvent) - if (showEvent.isDefaultPrevented()) { + if (showEvent.defaultPrevented) { return } @@ -91,29 +99,17 @@ class Toast { const complete = () => { this._element.classList.remove(CLASS_NAME_SHOWING) - this._element.classList.add(CLASS_NAME_SHOW) - - $(this._element).trigger(EVENT_SHOWN) + EventHandler.trigger(this._element, EVENT_SHOWN) - if (this._config.autohide) { - this._timeout = setTimeout(() => { - this.hide() - }, this._config.delay) - } + this._maybeScheduleHide() } - this._element.classList.remove(CLASS_NAME_HIDE) - Util.reflow(this._element) + this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated + reflow(this._element) + this._element.classList.add(CLASS_NAME_SHOW) this._element.classList.add(CLASS_NAME_SHOWING) - if (this._config.animation) { - const transitionDuration = Util.getTransitionDurationFromElement(this._element) - $(this._element) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) - } else { - complete() - } + this._queueCallback(complete, this._element, this._config.animation) } hide() { @@ -121,14 +117,21 @@ class Toast { return } - const hideEvent = $.Event(EVENT_HIDE) + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) - $(this._element).trigger(hideEvent) - if (hideEvent.isDefaultPrevented()) { + if (hideEvent.defaultPrevented) { return } - this._close() + const complete = () => { + this._element.classList.add(CLASS_NAME_HIDE) // @deprecated + this._element.classList.remove(CLASS_NAME_SHOWING) + this._element.classList.remove(CLASS_NAME_SHOW) + EventHandler.trigger(this._element, EVENT_HIDDEN) + } + + this._element.classList.add(CLASS_NAME_SHOWING) + this._queueCallback(complete, this._element, this._config.animation) } dispose() { @@ -138,11 +141,7 @@ class Toast { this._element.classList.remove(CLASS_NAME_SHOW) } - $(this._element).off(EVENT_CLICK_DISMISS) - - $.removeData(this._element, DATA_KEY) - this._element = null - this._config = null + super.dispose() } // Private @@ -150,39 +149,61 @@ class Toast { _getConfig(config) { config = { ...Default, - ...$(this._element).data(), + ...Manipulator.getDataAttributes(this._element), ...(typeof config === 'object' && config ? config : {}) } - Util.typeCheckConfig( - NAME, - config, - this.constructor.DefaultType - ) + typeCheckConfig(NAME, config, this.constructor.DefaultType) return config } - _setListeners() { - $(this._element).on(EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) + _maybeScheduleHide() { + if (!this._config.autohide) { + return + } + + if (this._hasMouseInteraction || this._hasKeyboardInteraction) { + return + } + + this._timeout = setTimeout(() => { + this.hide() + }, this._config.delay) } - _close() { - const complete = () => { - this._element.classList.add(CLASS_NAME_HIDE) - $(this._element).trigger(EVENT_HIDDEN) + _onInteraction(event, isInteracting) { + switch (event.type) { + case 'mouseover': + case 'mouseout': + this._hasMouseInteraction = isInteracting + break + case 'focusin': + case 'focusout': + this._hasKeyboardInteraction = isInteracting + break + default: + break } - this._element.classList.remove(CLASS_NAME_SHOW) - if (this._config.animation) { - const transitionDuration = Util.getTransitionDurationFromElement(this._element) + if (isInteracting) { + this._clearTimeout() + return + } - $(this._element) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) - } else { - complete() + const nextElement = event.relatedTarget + if (this._element === nextElement || this._element.contains(nextElement)) { + return } + + this._maybeScheduleHide() + } + + _setListeners() { + EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true)) + EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false)) + EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true)) + EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false)) } _clearTimeout() { @@ -192,16 +213,9 @@ class Toast { // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - const $element = $(this) - let data = $element.data(DATA_KEY) - const _config = typeof config === 'object' && config - - if (!data) { - data = new Toast(this, _config) - $element.data(DATA_KEY, data) - } + const data = Toast.getOrCreateInstance(this, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -214,17 +228,15 @@ class Toast { } } +enableDismissTrigger(Toast) + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Toast to jQuery only if jQuery is present */ -$.fn[NAME] = Toast._jQueryInterface -$.fn[NAME].Constructor = Toast -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Toast._jQueryInterface -} +defineJQueryPlugin(Toast) export default Toast diff --git a/vendor/twbs/bootstrap/js/src/tooltip.js b/vendor/twbs/bootstrap/js/src/tooltip.js index fd6ceea67..d8bb31a76 100644 --- a/vendor/twbs/bootstrap/js/src/tooltip.js +++ b/vendor/twbs/bootstrap/js/src/tooltip.js @@ -1,17 +1,28 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): tooltip.js + * Bootstrap (v5.1.3): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ +import * as Popper from '@popperjs/core' + import { - DefaultWhitelist, - sanitizeHtml -} from './tools/sanitizer' -import $ from 'jquery' -import Popper from 'popper.js' -import Util from './util' + defineJQueryPlugin, + findShadowRoot, + getElement, + getUID, + isElement, + isRTL, + noop, + typeCheckConfig +} from './util/index' +import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' +import BaseComponent from './base-component' /** * ------------------------------------------------------------------------ @@ -20,13 +31,10 @@ import Util from './util' */ const NAME = 'tooltip' -const VERSION = '4.6.0' const DATA_KEY = 'bs.tooltip' const EVENT_KEY = `.${DATA_KEY}` -const JQUERY_NO_CONFLICT = $.fn[NAME] const CLASS_PREFIX = 'bs-tooltip' -const BSCLS_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g') -const DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn'] +const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']) const DefaultType = { animation: 'boolean', @@ -37,50 +45,48 @@ const DefaultType = { html: 'boolean', selector: '(string|boolean)', placement: '(string|function)', - offset: '(number|string|function)', + offset: '(array|string|function)', container: '(string|element|boolean)', - fallbackPlacement: '(string|array)', + fallbackPlacements: 'array', boundary: '(string|element)', customClass: '(string|function)', sanitize: 'boolean', sanitizeFn: '(null|function)', - whiteList: 'object', - popperConfig: '(null|object)' + allowList: 'object', + popperConfig: '(null|object|function)' } const AttachmentMap = { AUTO: 'auto', TOP: 'top', - RIGHT: 'right', + RIGHT: isRTL() ? 'left' : 'right', BOTTOM: 'bottom', - LEFT: 'left' + LEFT: isRTL() ? 'right' : 'left' } const Default = { animation: true, template: '<div class="tooltip" role="tooltip">' + - '<div class="arrow"></div>' + - '<div class="tooltip-inner"></div></div>', + '<div class="tooltip-arrow"></div>' + + '<div class="tooltip-inner"></div>' + + '</div>', trigger: 'hover focus', title: '', delay: 0, html: false, selector: false, placement: 'top', - offset: 0, + offset: [0, 0], container: false, - fallbackPlacement: 'flip', - boundary: 'scrollParent', + fallbackPlacements: ['top', 'right', 'bottom', 'left'], + boundary: 'clippingParents', customClass: '', sanitize: true, sanitizeFn: null, - whiteList: DefaultWhitelist, + allowList: DefaultAllowlist, popperConfig: null } -const HOVER_STATE_SHOW = 'show' -const HOVER_STATE_OUT = 'out' - const Event = { HIDE: `hide${EVENT_KEY}`, HIDDEN: `hidden${EVENT_KEY}`, @@ -95,10 +101,16 @@ const Event = { } const CLASS_NAME_FADE = 'fade' +const CLASS_NAME_MODAL = 'modal' const CLASS_NAME_SHOW = 'show' +const HOVER_STATE_SHOW = 'show' +const HOVER_STATE_OUT = 'out' + const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' -const SELECTOR_ARROW = '.arrow' +const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` + +const EVENT_MODAL_HIDE = 'hide.bs.modal' const TRIGGER_HOVER = 'hover' const TRIGGER_FOCUS = 'focus' @@ -111,12 +123,14 @@ const TRIGGER_MANUAL = 'manual' * ------------------------------------------------------------------------ */ -class Tooltip { +class Tooltip extends BaseComponent { constructor(element, config) { if (typeof Popper === 'undefined') { throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)') } + super(element) + // private this._isEnabled = true this._timeout = 0 @@ -125,8 +139,7 @@ class Tooltip { this._popper = null // Protected - this.element = element - this.config = this._getConfig(config) + this._config = this._getConfig(config) this.tip = null this._setListeners() @@ -134,10 +147,6 @@ class Tooltip { // Getters - static get VERSION() { - return VERSION - } - static get Default() { return Default } @@ -146,18 +155,10 @@ class Tooltip { return NAME } - static get DATA_KEY() { - return DATA_KEY - } - static get Event() { return Event } - static get EVENT_KEY() { - return EVENT_KEY - } - static get DefaultType() { return DefaultType } @@ -182,16 +183,7 @@ class Tooltip { } if (event) { - const dataKey = this.constructor.DATA_KEY - let context = $(event.currentTarget).data(dataKey) - - if (!context) { - context = new this.constructor( - event.currentTarget, - this._getDelegateConfig() - ) - $(event.currentTarget).data(dataKey, context) - } + const context = this._initializeOnDelegatedTarget(event) context._activeTrigger.click = !context._activeTrigger.click @@ -201,7 +193,7 @@ class Tooltip { context._leave(null, context) } } else { - if ($(this.getTipElement()).hasClass(CLASS_NAME_SHOW)) { + if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) { this._leave(null, this) return } @@ -213,170 +205,154 @@ class Tooltip { dispose() { clearTimeout(this._timeout) - $.removeData(this.element, this.constructor.DATA_KEY) - - $(this.element).off(this.constructor.EVENT_KEY) - $(this.element).closest('.modal').off('hide.bs.modal', this._hideModalHandler) + EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler) if (this.tip) { - $(this.tip).remove() - } - - this._isEnabled = null - this._timeout = null - this._hoverState = null - this._activeTrigger = null - if (this._popper) { - this._popper.destroy() + this.tip.remove() } - this._popper = null - this.element = null - this.config = null - this.tip = null + this._disposePopper() + super.dispose() } show() { - if ($(this.element).css('display') === 'none') { + if (this._element.style.display === 'none') { throw new Error('Please use show on visible elements') } - const showEvent = $.Event(this.constructor.Event.SHOW) - if (this.isWithContent() && this._isEnabled) { - $(this.element).trigger(showEvent) - - const shadowRoot = Util.findShadowRoot(this.element) - const isInTheDom = $.contains( - shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement, - this.element - ) + if (!(this.isWithContent() && this._isEnabled)) { + return + } - if (showEvent.isDefaultPrevented() || !isInTheDom) { - return - } + const showEvent = EventHandler.trigger(this._element, this.constructor.Event.SHOW) + const shadowRoot = findShadowRoot(this._element) + const isInTheDom = shadowRoot === null ? + this._element.ownerDocument.documentElement.contains(this._element) : + shadowRoot.contains(this._element) - const tip = this.getTipElement() - const tipId = Util.getUID(this.constructor.NAME) + if (showEvent.defaultPrevented || !isInTheDom) { + return + } - tip.setAttribute('id', tipId) - this.element.setAttribute('aria-describedby', tipId) + // A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title` + // This will be removed later in favor of a `setContent` method + if (this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML) { + this._disposePopper() + this.tip.remove() + this.tip = null + } - this.setContent() + const tip = this.getTipElement() + const tipId = getUID(this.constructor.NAME) - if (this.config.animation) { - $(tip).addClass(CLASS_NAME_FADE) - } + tip.setAttribute('id', tipId) + this._element.setAttribute('aria-describedby', tipId) - const placement = typeof this.config.placement === 'function' ? - this.config.placement.call(this, tip, this.element) : - this.config.placement + if (this._config.animation) { + tip.classList.add(CLASS_NAME_FADE) + } - const attachment = this._getAttachment(placement) - this.addAttachmentClass(attachment) + const placement = typeof this._config.placement === 'function' ? + this._config.placement.call(this, tip, this._element) : + this._config.placement - const container = this._getContainer() - $(tip).data(this.constructor.DATA_KEY, this) + const attachment = this._getAttachment(placement) + this._addAttachmentClass(attachment) - if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) { - $(tip).appendTo(container) - } + const { container } = this._config + Data.set(tip, this.constructor.DATA_KEY, this) - $(this.element).trigger(this.constructor.Event.INSERTED) + if (!this._element.ownerDocument.documentElement.contains(this.tip)) { + container.append(tip) + EventHandler.trigger(this._element, this.constructor.Event.INSERTED) + } - this._popper = new Popper(this.element, tip, this._getPopperConfig(attachment)) + if (this._popper) { + this._popper.update() + } else { + this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) + } - $(tip).addClass(CLASS_NAME_SHOW) - $(tip).addClass(this.config.customClass) + tip.classList.add(CLASS_NAME_SHOW) - // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - if ('ontouchstart' in document.documentElement) { - $(document.body).children().on('mouseover', null, $.noop) - } + const customClass = this._resolvePossibleFunction(this._config.customClass) + if (customClass) { + tip.classList.add(...customClass.split(' ')) + } - const complete = () => { - if (this.config.animation) { - this._fixTransition() - } + // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + if ('ontouchstart' in document.documentElement) { + [].concat(...document.body.children).forEach(element => { + EventHandler.on(element, 'mouseover', noop) + }) + } - const prevHoverState = this._hoverState - this._hoverState = null + const complete = () => { + const prevHoverState = this._hoverState - $(this.element).trigger(this.constructor.Event.SHOWN) + this._hoverState = null + EventHandler.trigger(this._element, this.constructor.Event.SHOWN) - if (prevHoverState === HOVER_STATE_OUT) { - this._leave(null, this) - } + if (prevHoverState === HOVER_STATE_OUT) { + this._leave(null, this) } + } - if ($(this.tip).hasClass(CLASS_NAME_FADE)) { - const transitionDuration = Util.getTransitionDurationFromElement(this.tip) + const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) + this._queueCallback(complete, this.tip, isAnimated) + } - $(this.tip) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) - } else { - complete() - } + hide() { + if (!this._popper) { + return } - } - hide(callback) { const tip = this.getTipElement() - const hideEvent = $.Event(this.constructor.Event.HIDE) const complete = () => { - if (this._hoverState !== HOVER_STATE_SHOW && tip.parentNode) { - tip.parentNode.removeChild(tip) + if (this._isWithActiveTrigger()) { + return } - this._cleanTipClass() - this.element.removeAttribute('aria-describedby') - $(this.element).trigger(this.constructor.Event.HIDDEN) - if (this._popper !== null) { - this._popper.destroy() + if (this._hoverState !== HOVER_STATE_SHOW) { + tip.remove() } - if (callback) { - callback() - } - } + this._cleanTipClass() + this._element.removeAttribute('aria-describedby') + EventHandler.trigger(this._element, this.constructor.Event.HIDDEN) - $(this.element).trigger(hideEvent) + this._disposePopper() + } - if (hideEvent.isDefaultPrevented()) { + const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE) + if (hideEvent.defaultPrevented) { return } - $(tip).removeClass(CLASS_NAME_SHOW) + tip.classList.remove(CLASS_NAME_SHOW) // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { - $(document.body).children().off('mouseover', null, $.noop) + [].concat(...document.body.children) + .forEach(element => EventHandler.off(element, 'mouseover', noop)) } this._activeTrigger[TRIGGER_CLICK] = false this._activeTrigger[TRIGGER_FOCUS] = false this._activeTrigger[TRIGGER_HOVER] = false - if ($(this.tip).hasClass(CLASS_NAME_FADE)) { - const transitionDuration = Util.getTransitionDurationFromElement(tip) - - $(tip) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) - } else { - complete() - } - + const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) + this._queueCallback(complete, this.tip, isAnimated) this._hoverState = '' } update() { if (this._popper !== null) { - this._popper.scheduleUpdate() + this._popper.update() } } @@ -386,118 +362,162 @@ class Tooltip { return Boolean(this.getTitle()) } - addAttachmentClass(attachment) { - $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`) - } - getTipElement() { - this.tip = this.tip || $(this.config.template)[0] + if (this.tip) { + return this.tip + } + + const element = document.createElement('div') + element.innerHTML = this._config.template + + const tip = element.children[0] + this.setContent(tip) + tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) + + this.tip = tip return this.tip } - setContent() { - const tip = this.getTipElement() - this.setElementContent($(tip.querySelectorAll(SELECTOR_TOOLTIP_INNER)), this.getTitle()) - $(tip).removeClass(`${CLASS_NAME_FADE} ${CLASS_NAME_SHOW}`) + setContent(tip) { + this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER) } - setElementContent($element, content) { - if (typeof content === 'object' && (content.nodeType || content.jquery)) { - // Content is a DOM node or a jQuery - if (this.config.html) { - if (!$(content).parent().is($element)) { - $element.empty().append(content) + _sanitizeAndSetContent(template, content, selector) { + const templateElement = SelectorEngine.findOne(selector, template) + + if (!content && templateElement) { + templateElement.remove() + return + } + + // we use append for html objects to maintain js events + this.setElementContent(templateElement, content) + } + + setElementContent(element, content) { + if (element === null) { + return + } + + if (isElement(content)) { + content = getElement(content) + + // content is a DOM node or a jQuery + if (this._config.html) { + if (content.parentNode !== element) { + element.innerHTML = '' + element.append(content) } } else { - $element.text($(content).text()) + element.textContent = content.textContent } return } - if (this.config.html) { - if (this.config.sanitize) { - content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn) + if (this._config.html) { + if (this._config.sanitize) { + content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn) } - $element.html(content) + element.innerHTML = content } else { - $element.text(content) + element.textContent = content } } getTitle() { - let title = this.element.getAttribute('data-original-title') + const title = this._element.getAttribute('data-bs-original-title') || this._config.title + + return this._resolvePossibleFunction(title) + } + + updateAttachment(attachment) { + if (attachment === 'right') { + return 'end' + } - if (!title) { - title = typeof this.config.title === 'function' ? - this.config.title.call(this.element) : - this.config.title + if (attachment === 'left') { + return 'start' } - return title + return attachment } // Private - _getPopperConfig(attachment) { - const defaultBsConfig = { - placement: attachment, - modifiers: { - offset: this._getOffset(), - flip: { - behavior: this.config.fallbackPlacement - }, - arrow: { - element: SELECTOR_ARROW - }, - preventOverflow: { - boundariesElement: this.config.boundary - } - }, - onCreate: data => { - if (data.originalPlacement !== data.placement) { - this._handlePopperPlacementChange(data) - } - }, - onUpdate: data => this._handlePopperPlacementChange(data) - } - - return { - ...defaultBsConfig, - ...this.config.popperConfig - } + _initializeOnDelegatedTarget(event, context) { + return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) } _getOffset() { - const offset = {} + const { offset } = this._config - if (typeof this.config.offset === 'function') { - offset.fn = data => { - data.offsets = { - ...data.offsets, - ...(this.config.offset(data.offsets, this.element) || {}) - } + if (typeof offset === 'string') { + return offset.split(',').map(val => Number.parseInt(val, 10)) + } - return data - } - } else { - offset.offset = this.config.offset + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element) } return offset } - _getContainer() { - if (this.config.container === false) { - return document.body + _resolvePossibleFunction(content) { + return typeof content === 'function' ? content.call(this._element) : content + } + + _getPopperConfig(attachment) { + const defaultBsPopperConfig = { + placement: attachment, + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: this._config.fallbackPlacements + } + }, + { + name: 'offset', + options: { + offset: this._getOffset() + } + }, + { + name: 'preventOverflow', + options: { + boundary: this._config.boundary + } + }, + { + name: 'arrow', + options: { + element: `.${this.constructor.NAME}-arrow` + } + }, + { + name: 'onChange', + enabled: true, + phase: 'afterWrite', + fn: data => this._handlePopperPlacementChange(data) + } + ], + onFirstUpdate: data => { + if (data.options.placement !== data.placement) { + this._handlePopperPlacementChange(data) + } + } } - if (Util.isElement(this.config.container)) { - return $(this.config.container) + return { + ...defaultBsPopperConfig, + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) } + } - return $(document).find(this.config.container) + _addAttachmentClass(attachment) { + this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`) } _getAttachment(placement) { @@ -505,15 +525,11 @@ class Tooltip { } _setListeners() { - const triggers = this.config.trigger.split(' ') + const triggers = this._config.trigger.split(' ') triggers.forEach(trigger => { if (trigger === 'click') { - $(this.element).on( - this.constructor.Event.CLICK, - this.config.selector, - event => this.toggle(event) - ) + EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event)) } else if (trigger !== TRIGGER_MANUAL) { const eventIn = trigger === TRIGGER_HOVER ? this.constructor.Event.MOUSEENTER : @@ -522,23 +538,22 @@ class Tooltip { this.constructor.Event.MOUSELEAVE : this.constructor.Event.FOCUSOUT - $(this.element) - .on(eventIn, this.config.selector, event => this._enter(event)) - .on(eventOut, this.config.selector, event => this._leave(event)) + EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event)) + EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event)) } }) this._hideModalHandler = () => { - if (this.element) { + if (this._element) { this.hide() } } - $(this.element).closest('.modal').on('hide.bs.modal', this._hideModalHandler) + EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler) - if (this.config.selector) { - this.config = { - ...this.config, + if (this._config.selector) { + this._config = { + ...this._config, trigger: 'manual', selector: '' } @@ -548,29 +563,21 @@ class Tooltip { } _fixTitle() { - const titleType = typeof this.element.getAttribute('data-original-title') + const title = this._element.getAttribute('title') + const originalTitleType = typeof this._element.getAttribute('data-bs-original-title') - if (this.element.getAttribute('title') || titleType !== 'string') { - this.element.setAttribute( - 'data-original-title', - this.element.getAttribute('title') || '' - ) + if (title || originalTitleType !== 'string') { + this._element.setAttribute('data-bs-original-title', title || '') + if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { + this._element.setAttribute('aria-label', title) + } - this.element.setAttribute('title', '') + this._element.setAttribute('title', '') } } _enter(event, context) { - const dataKey = this.constructor.DATA_KEY - context = context || $(event.currentTarget).data(dataKey) - - if (!context) { - context = new this.constructor( - event.currentTarget, - this._getDelegateConfig() - ) - $(event.currentTarget).data(dataKey, context) - } + context = this._initializeOnDelegatedTarget(event, context) if (event) { context._activeTrigger[ @@ -578,7 +585,7 @@ class Tooltip { ] = true } - if ($(context.getTipElement()).hasClass(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) { + if (context.getTipElement().classList.contains(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) { context._hoverState = HOVER_STATE_SHOW return } @@ -587,7 +594,7 @@ class Tooltip { context._hoverState = HOVER_STATE_SHOW - if (!context.config.delay || !context.config.delay.show) { + if (!context._config.delay || !context._config.delay.show) { context.show() return } @@ -596,25 +603,16 @@ class Tooltip { if (context._hoverState === HOVER_STATE_SHOW) { context.show() } - }, context.config.delay.show) + }, context._config.delay.show) } _leave(event, context) { - const dataKey = this.constructor.DATA_KEY - context = context || $(event.currentTarget).data(dataKey) - - if (!context) { - context = new this.constructor( - event.currentTarget, - this._getDelegateConfig() - ) - $(event.currentTarget).data(dataKey, context) - } + context = this._initializeOnDelegatedTarget(event, context) if (event) { context._activeTrigger[ event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER - ] = false + ] = context._element.contains(event.relatedTarget) } if (context._isWithActiveTrigger()) { @@ -625,7 +623,7 @@ class Tooltip { context._hoverState = HOVER_STATE_OUT - if (!context.config.delay || !context.config.delay.hide) { + if (!context._config.delay || !context._config.delay.hide) { context.hide() return } @@ -634,7 +632,7 @@ class Tooltip { if (context._hoverState === HOVER_STATE_OUT) { context.hide() } - }, context.config.delay.hide) + }, context._config.delay.hide) } _isWithActiveTrigger() { @@ -648,14 +646,13 @@ class Tooltip { } _getConfig(config) { - const dataAttributes = $(this.element).data() + const dataAttributes = Manipulator.getDataAttributes(this._element) - Object.keys(dataAttributes) - .forEach(dataAttr => { - if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) { - delete dataAttributes[dataAttr] - } - }) + Object.keys(dataAttributes).forEach(dataAttr => { + if (DISALLOWED_ATTRIBUTES.has(dataAttr)) { + delete dataAttributes[dataAttr] + } + }) config = { ...this.constructor.Default, @@ -663,6 +660,8 @@ class Tooltip { ...(typeof config === 'object' && config ? config : {}) } + config.container = config.container === false ? document.body : getElement(config.container) + if (typeof config.delay === 'number') { config.delay = { show: config.delay, @@ -678,14 +677,10 @@ class Tooltip { config.content = config.content.toString() } - Util.typeCheckConfig( - NAME, - config, - this.constructor.DefaultType - ) + typeCheckConfig(NAME, config, this.constructor.DefaultType) if (config.sanitize) { - config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn) + config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn) } return config @@ -694,62 +689,56 @@ class Tooltip { _getDelegateConfig() { const config = {} - if (this.config) { - for (const key in this.config) { - if (this.constructor.Default[key] !== this.config[key]) { - config[key] = this.config[key] - } + for (const key in this._config) { + if (this.constructor.Default[key] !== this._config[key]) { + config[key] = this._config[key] } } + // In the future can be replaced with: + // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) + // `Object.fromEntries(keysWithDifferentValues)` return config } _cleanTipClass() { - const $tip = $(this.getTipElement()) - const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX) - if (tabClass !== null && tabClass.length) { - $tip.removeClass(tabClass.join('')) + const tip = this.getTipElement() + const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g') + const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex) + if (tabClass !== null && tabClass.length > 0) { + tabClass.map(token => token.trim()) + .forEach(tClass => tip.classList.remove(tClass)) } } - _handlePopperPlacementChange(popperData) { - this.tip = popperData.instance.popper - this._cleanTipClass() - this.addAttachmentClass(this._getAttachment(popperData.placement)) + _getBasicClassPrefix() { + return CLASS_PREFIX } - _fixTransition() { - const tip = this.getTipElement() - const initConfigAnimation = this.config.animation + _handlePopperPlacementChange(popperData) { + const { state } = popperData - if (tip.getAttribute('x-placement') !== null) { + if (!state) { return } - $(tip).removeClass(CLASS_NAME_FADE) - this.config.animation = false - this.hide() - this.show() - this.config.animation = initConfigAnimation + this.tip = state.elements.popper + this._cleanTipClass() + this._addAttachmentClass(this._getAttachment(state.placement)) + } + + _disposePopper() { + if (this._popper) { + this._popper.destroy() + this._popper = null + } } // Static - static _jQueryInterface(config) { + static jQueryInterface(config) { return this.each(function () { - const $element = $(this) - let data = $element.data(DATA_KEY) - const _config = typeof config === 'object' && config - - if (!data && /dispose|hide/.test(config)) { - return - } - - if (!data) { - data = new Tooltip(this, _config) - $element.data(DATA_KEY, data) - } + const data = Tooltip.getOrCreateInstance(this, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { @@ -766,13 +755,9 @@ class Tooltip { * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .Tooltip to jQuery only if jQuery is present */ -$.fn[NAME] = Tooltip._jQueryInterface -$.fn[NAME].Constructor = Tooltip -$.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Tooltip._jQueryInterface -} +defineJQueryPlugin(Tooltip) export default Tooltip diff --git a/vendor/twbs/bootstrap/js/src/util.js b/vendor/twbs/bootstrap/js/src/util.js deleted file mode 100644 index cd9d5e3b8..000000000 --- a/vendor/twbs/bootstrap/js/src/util.js +++ /dev/null @@ -1,198 +0,0 @@ -/** - * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): util.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - -import $ from 'jquery' - -/** - * ------------------------------------------------------------------------ - * Private TransitionEnd Helpers - * ------------------------------------------------------------------------ - */ - -const TRANSITION_END = 'transitionend' -const MAX_UID = 1000000 -const MILLISECONDS_MULTIPLIER = 1000 - -// Shoutout AngusCroll (https://goo.gl/pxwQGp) -function toType(obj) { - if (obj === null || typeof obj === 'undefined') { - return `${obj}` - } - - return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase() -} - -function getSpecialTransitionEndEvent() { - return { - bindType: TRANSITION_END, - delegateType: TRANSITION_END, - handle(event) { - if ($(event.target).is(this)) { - return event.handleObj.handler.apply(this, arguments) // eslint-disable-line prefer-rest-params - } - - return undefined - } - } -} - -function transitionEndEmulator(duration) { - let called = false - - $(this).one(Util.TRANSITION_END, () => { - called = true - }) - - setTimeout(() => { - if (!called) { - Util.triggerTransitionEnd(this) - } - }, duration) - - return this -} - -function setTransitionEndSupport() { - $.fn.emulateTransitionEnd = transitionEndEmulator - $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent() -} - -/** - * -------------------------------------------------------------------------- - * Public Util Api - * -------------------------------------------------------------------------- - */ - -const Util = { - TRANSITION_END: 'bsTransitionEnd', - - getUID(prefix) { - do { - prefix += ~~(Math.random() * MAX_UID) // "~~" acts like a faster Math.floor() here - } while (document.getElementById(prefix)) - - return prefix - }, - - getSelectorFromElement(element) { - let selector = element.getAttribute('data-target') - - if (!selector || selector === '#') { - const hrefAttr = element.getAttribute('href') - selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : '' - } - - try { - return document.querySelector(selector) ? selector : null - } catch (_) { - return null - } - }, - - getTransitionDurationFromElement(element) { - if (!element) { - return 0 - } - - // Get transition-duration of the element - let transitionDuration = $(element).css('transition-duration') - let transitionDelay = $(element).css('transition-delay') - - const floatTransitionDuration = parseFloat(transitionDuration) - const floatTransitionDelay = 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 (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER - }, - - reflow(element) { - return element.offsetHeight - }, - - triggerTransitionEnd(element) { - $(element).trigger(TRANSITION_END) - }, - - supportsTransitionEnd() { - return Boolean(TRANSITION_END) - }, - - isElement(obj) { - return (obj[0] || obj).nodeType - }, - - typeCheckConfig(componentName, config, configTypes) { - for (const property in configTypes) { - if (Object.prototype.hasOwnProperty.call(configTypes, property)) { - const expectedTypes = configTypes[property] - const value = config[property] - const valueType = value && Util.isElement(value) ? - 'element' : toType(value) - - if (!new RegExp(expectedTypes).test(valueType)) { - throw new Error( - `${componentName.toUpperCase()}: ` + - `Option "${property}" provided type "${valueType}" ` + - `but expected type "${expectedTypes}".`) - } - } - } - }, - - 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 Util.findShadowRoot(element.parentNode) - }, - - jQueryDetection() { - if (typeof $ === 'undefined') { - throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.') - } - - const version = $.fn.jquery.split(' ')[0].split('.') - const minMajor = 1 - const ltMajor = 2 - const minMinor = 9 - const minPatch = 1 - const maxMajor = 4 - - if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) { - throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0') - } - } -} - -Util.jQueryDetection() -setTransitionEndSupport() - -export default Util diff --git a/vendor/twbs/bootstrap/js/src/util/backdrop.js b/vendor/twbs/bootstrap/js/src/util/backdrop.js new file mode 100644 index 000000000..04c763518 --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/util/backdrop.js @@ -0,0 +1,130 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): 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' + +const Default = { + className: 'modal-backdrop', + 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 = { + className: 'string', + isVisible: 'boolean', + isAnimated: 'boolean', + 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 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 = this._config.className + 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.append(this._getElement()) + + EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => { + execute(this._config.clickCallback) + }) + + this._isAppended = true + } + + dispose() { + if (!this._isAppended) { + return + } + + EventHandler.off(this._element, EVENT_MOUSEDOWN) + + this._element.remove() + this._isAppended = false + } + + _emulateAnimation(callback) { + executeAfterTransition(callback, this._getElement(), this._config.isAnimated) + } +} + +export default Backdrop diff --git a/vendor/twbs/bootstrap/js/src/util/component-functions.js b/vendor/twbs/bootstrap/js/src/util/component-functions.js new file mode 100644 index 000000000..bd44c3fdc --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/util/component-functions.js @@ -0,0 +1,34 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): 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' + +const enableDismissTrigger = (component, method = 'hide') => { + const clickEvent = `click.dismiss${component.EVENT_KEY}` + const name = component.NAME + + EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { + if (['A', 'AREA'].includes(this.tagName)) { + event.preventDefault() + } + + if (isDisabled(this)) { + return + } + + const target = 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 + instance[method]() + }) +} + +export { + enableDismissTrigger +} diff --git a/vendor/twbs/bootstrap/js/src/util/focustrap.js b/vendor/twbs/bootstrap/js/src/util/focustrap.js new file mode 100644 index 000000000..44d5f47eb --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/util/focustrap.js @@ -0,0 +1,105 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): 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' + +const Default = { + trapElement: null, // The element to trap focus inside of + autofocus: true +} + +const DefaultType = { + trapElement: 'element', + autofocus: 'boolean' +} + +const NAME = 'focustrap' +const DATA_KEY = 'bs.focustrap' +const EVENT_KEY = `.${DATA_KEY}` +const EVENT_FOCUSIN = `focusin${EVENT_KEY}` +const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}` + +const TAB_KEY = 'Tab' +const TAB_NAV_FORWARD = 'forward' +const TAB_NAV_BACKWARD = 'backward' + +class FocusTrap { + constructor(config) { + this._config = this._getConfig(config) + this._isActive = false + this._lastTabNavDirection = null + } + + activate() { + const { trapElement, autofocus } = this._config + + if (this._isActive) { + return + } + + if (autofocus) { + trapElement.focus() + } + + EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop + EventHandler.on(document, EVENT_FOCUSIN, event => this._handleFocusin(event)) + EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)) + + this._isActive = true + } + + deactivate() { + if (!this._isActive) { + return + } + + this._isActive = false + EventHandler.off(document, EVENT_KEY) + } + + // Private + + _handleFocusin(event) { + const { target } = event + const { trapElement } = this._config + + if (target === document || target === trapElement || trapElement.contains(target)) { + return + } + + const elements = SelectorEngine.focusableChildren(trapElement) + + if (elements.length === 0) { + trapElement.focus() + } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { + elements[elements.length - 1].focus() + } else { + elements[0].focus() + } + } + + _handleKeydown(event) { + if (event.key !== TAB_KEY) { + return + } + + 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/vendor/twbs/bootstrap/js/src/util/index.js b/vendor/twbs/bootstrap/js/src/util/index.js new file mode 100644 index 000000000..d05a3cbd7 --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/util/index.js @@ -0,0 +1,333 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): util/index.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +const MAX_UID = 1000000 +const MILLISECONDS_MULTIPLIER = 1000 +const TRANSITION_END = 'transitionend' + +// Shoutout AngusCroll (https://goo.gl/pxwQGp) +const toType = obj => { + if (obj === null || obj === undefined) { + return `${obj}` + } + + return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase() +} + +/** + * -------------------------------------------------------------------------- + * Public Util Api + * -------------------------------------------------------------------------- + */ + +const getUID = prefix => { + do { + prefix += Math.floor(Math.random() * MAX_UID) + } while (document.getElementById(prefix)) + + return prefix +} + +const getSelector = element => { + let selector = element.getAttribute('data-bs-target') + + if (!selector || selector === '#') { + let hrefAttr = element.getAttribute('href') + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) { + return null + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { + hrefAttr = `#${hrefAttr.split('#')[1]}` + } + + selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null + } + + return selector +} + +const getSelectorFromElement = element => { + const selector = getSelector(element) + + if (selector) { + return document.querySelector(selector) ? selector : null + } + + return null +} + +const getElementFromSelector = element => { + const selector = getSelector(element) + + return selector ? document.querySelector(selector) : null +} + +const getTransitionDurationFromElement = element => { + if (!element) { + return 0 + } + + // Get transition-duration of the element + let { transitionDuration, transitionDelay } = window.getComputedStyle(element) + + const floatTransitionDuration = Number.parseFloat(transitionDuration) + const floatTransitionDelay = Number.parseFloat(transitionDelay) + + // Return 0 if element or transition duration is not found + if (!floatTransitionDuration && !floatTransitionDelay) { + return 0 + } + + // If multiple durations are defined, take the first + transitionDuration = transitionDuration.split(',')[0] + transitionDelay = transitionDelay.split(',')[0] + + return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER +} + +const triggerTransitionEnd = element => { + element.dispatchEvent(new Event(TRANSITION_END)) +} + +const isElement = obj => { + if (!obj || typeof obj !== 'object') { + return false + } + + if (typeof obj.jquery !== 'undefined') { + obj = obj[0] + } + + return typeof obj.nodeType !== 'undefined' +} + +const getElement = obj => { + if (isElement(obj)) { // it's a jQuery object or a node element + return obj.jquery ? obj[0] : obj + } + + if (typeof obj === 'string' && obj.length > 0) { + return document.querySelector(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 = () => {} + +/** + * Trick to restart an element's animation + * + * @param {HTMLElement} element + * @return void + * + * @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 +} + +const getjQuery = () => { + const { jQuery } = window + + if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { + return jQuery + } + + return null +} + +const DOMContentLoadedCallbacks = [] + +const onDOMContentLoaded = callback => { + if (document.readyState === 'loading') { + // add listener on the first call when the document is in loading state + if (!DOMContentLoadedCallbacks.length) { + document.addEventListener('DOMContentLoaded', () => { + DOMContentLoadedCallbacks.forEach(callback => callback()) + }) + } + + DOMContentLoadedCallbacks.push(callback) + } else { + callback() + } +} + +const isRTL = () => document.documentElement.dir === 'rtl' + +const defineJQueryPlugin = plugin => { + onDOMContentLoaded(() => { + const $ = getjQuery() + /* istanbul ignore if */ + if ($) { + const name = plugin.NAME + const JQUERY_NO_CONFLICT = $.fn[name] + $.fn[name] = plugin.jQueryInterface + $.fn[name].Constructor = plugin + $.fn[name].noConflict = () => { + $.fn[name] = JQUERY_NO_CONFLICT + return plugin.jQueryInterface + } + } + }) +} + +const execute = callback => { + if (typeof callback === 'function') { + callback() + } +} + +const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if (!waitForTransition) { + execute(callback) + return + } + + const durationPadding = 5 + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding + + let called = false + + const handler = ({ target }) => { + if (target !== transitionElement) { + return + } + + called = true + transitionElement.removeEventListener(TRANSITION_END, handler) + execute(callback) + } + + transitionElement.addEventListener(TRANSITION_END, handler) + setTimeout(() => { + if (!called) { + triggerTransitionEnd(transitionElement) + } + }, emulatedDuration) +} + +/** + * Return the previous/next element of a list. + * + * @param {array} list The list of elements + * @param activeElement The active element + * @param shouldGetNext Choose to get next or previous element + * @param isCycleAllowed + * @return {Element|elem} The proper element + */ +const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + let index = list.indexOf(activeElement) + + // if the element does not exist in the list return an element depending on the direction and if cycle is allowed + if (index === -1) { + return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0] + } + + const listLength = list.length + + index += shouldGetNext ? 1 : -1 + + if (isCycleAllowed) { + index = (index + listLength) % listLength + } + + return list[Math.max(0, Math.min(index, listLength - 1))] +} + +export { + getElement, + getUID, + getSelectorFromElement, + getElementFromSelector, + getTransitionDurationFromElement, + triggerTransitionEnd, + isElement, + typeCheckConfig, + isVisible, + isDisabled, + findShadowRoot, + noop, + getNextActiveElement, + reflow, + getjQuery, + onDOMContentLoaded, + isRTL, + defineJQueryPlugin, + execute, + executeAfterTransition +} diff --git a/vendor/twbs/bootstrap/js/src/tools/sanitizer.js b/vendor/twbs/bootstrap/js/src/util/sanitizer.js index 45fd6106c..339c916c6 100644 --- a/vendor/twbs/bootstrap/js/src/tools/sanitizer.js +++ b/vendor/twbs/bootstrap/js/src/util/sanitizer.js @@ -1,11 +1,11 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v4.6.0): tools/sanitizer.js + * Bootstrap (v5.1.3): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -const uriAttrs = [ +const uriAttributes = new Set([ 'background', 'cite', 'href', @@ -14,11 +14,48 @@ const uriAttrs = [ 'poster', 'src', 'xlink:href' -] +]) const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i -export const DefaultWhitelist = { +/** + * 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 + } + + const regExp = allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp) + + // Check if a regular expression validates the attribute. + for (let i = 0, len = regExp.length; i < len; i++) { + if (regExp[i].test(attributeName)) { + return true + } + } + + return false +} + +export const DefaultAllowlist = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], a: ['target', 'href', 'title', 'rel'], @@ -52,45 +89,8 @@ export const DefaultWhitelist = { ul: [] } -/** - * 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):|[^#&/:?]*(?:[#/?]|$))/gi - -/** - * 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 - -function allowedAttribute(attr, allowedAttributeList) { - const attrName = attr.nodeName.toLowerCase() - - if (allowedAttributeList.indexOf(attrName) !== -1) { - if (uriAttrs.indexOf(attrName) !== -1) { - return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)) - } - - 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 (attrName.match(regExp[i])) { - return true - } - } - - return false -} - -export function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) { - if (unsafeHtml.length === 0) { +export function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) { + if (!unsafeHtml.length) { return unsafeHtml } @@ -100,25 +100,24 @@ export function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) { const domParser = new window.DOMParser() const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html') - const whitelistKeys = Object.keys(whiteList) - const elements = [].slice.call(createdDocument.body.querySelectorAll('*')) + 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() + const element = elements[i] + const elementName = element.nodeName.toLowerCase() - if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) { - el.parentNode.removeChild(el) + if (!Object.keys(allowList).includes(elementName)) { + element.remove() continue } - const attributeList = [].slice.call(el.attributes) - const whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []) + const attributeList = [].concat(...element.attributes) + const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []) - attributeList.forEach(attr => { - if (!allowedAttribute(attr, whitelistedAttributes)) { - el.removeAttribute(attr.nodeName) + attributeList.forEach(attribute => { + if (!allowedAttribute(attribute, allowedAttributes)) { + element.removeAttribute(attribute.nodeName) } }) } diff --git a/vendor/twbs/bootstrap/js/src/util/scrollbar.js b/vendor/twbs/bootstrap/js/src/util/scrollbar.js new file mode 100644 index 000000000..a90f21a79 --- /dev/null +++ b/vendor/twbs/bootstrap/js/src/util/scrollbar.js @@ -0,0 +1,97 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): util/scrollBar.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import SelectorEngine from '../dom/selector-engine' +import Manipulator from '../dom/manipulator' +import { isElement } from './index' + +const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' +const SELECTOR_STICKY_CONTENT = '.sticky-top' + +class ScrollBarHelper { + constructor() { + this._element = document.body + } + + getWidth() { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth + return Math.abs(window.innerWidth - documentWidth) + } + + hide() { + const width = this.getWidth() + this._disableOverFlow() + // give padding to element to balance the hidden scrollbar width + this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width) + // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) + this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) + } + + _disableOverFlow() { + this._saveInitialAttribute(this._element, 'overflow') + this._element.style.overflow = 'hidden' + } + + _setElementAttributes(selector, styleProp, callback) { + const scrollbarWidth = this.getWidth() + const manipulationCallBack = element => { + if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { + return + } + + this._saveInitialAttribute(element, styleProp) + const calculatedValue = window.getComputedStyle(element)[styleProp] + element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` + } + + this._applyManipulationCallback(selector, manipulationCallBack) + } + + reset() { + this._resetElementAttributes(this._element, 'overflow') + this._resetElementAttributes(this._element, 'paddingRight') + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') + } + + _saveInitialAttribute(element, styleProp) { + const actualValue = element.style[styleProp] + if (actualValue) { + Manipulator.setDataAttribute(element, styleProp, actualValue) + } + } + + _resetElementAttributes(selector, styleProp) { + const manipulationCallBack = element => { + const value = Manipulator.getDataAttribute(element, styleProp) + if (typeof value === 'undefined') { + element.style.removeProperty(styleProp) + } else { + Manipulator.removeDataAttribute(element, styleProp) + element.style[styleProp] = value + } + } + + this._applyManipulationCallback(selector, manipulationCallBack) + } + + _applyManipulationCallback(selector, callBack) { + if (isElement(selector)) { + callBack(selector) + } else { + SelectorEngine.find(selector, this._element).forEach(callBack) + } + } + + isOverflowing() { + return this.getWidth() > 0 + } +} + +export default ScrollBarHelper |