Compare commits
2 commits
04448b979f
...
ef51834f90
Author | SHA1 | Date | |
---|---|---|---|
ef51834f90 | |||
31122da91b |
229 changed files with 39838 additions and 1 deletions
|
@ -6,7 +6,7 @@ Héberger sur Acast (https://shows.acast.com/le-plus-beau-des-voyages)
|
|||
|
||||
Disponible à l'écoute sur :
|
||||
|
||||
- [RSS Feed](https://feeds.acast.com/public/shows/le-plus-beau-des-voyages)
|
||||
- Tout vos lecteur de flux [RSS](https://feeds.acast.com/public/shows/le-plus-beau-des-voyages)
|
||||
- [Apple podcast](https://podcasts.apple.com/fr/podcast/le-plus-beau-des-voyages/id1690552189)
|
||||
- [Amazon music](https://music.amazon.com/podcasts/2328ada5-4c12-4ecc-afcb-5153156fc437/le-plus-beau-des-voyages)
|
||||
- [Google podcast](https://podcasts.google.com/feed/aHR0cHM6Ly9mZWVkcy5hY2FzdC5jb20vcHVibGljL3Nob3dzL2xlLXBsdXMtYmVhdS1kZXMtdm95YWdlcw)
|
||||
|
|
90
bootstrap/js/dist/alert.js
vendored
Normal file
90
bootstrap/js/dist/alert.js
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*!
|
||||
* Bootstrap alert.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Alert = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions, global.Index));
|
||||
})(this, (function (BaseComponent, EventHandler, componentFunctions_js, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap alert.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'alert';
|
||||
const DATA_KEY = 'bs.alert';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const EVENT_CLOSE = `close${EVENT_KEY}`;
|
||||
const EVENT_CLOSED = `closed${EVENT_KEY}`;
|
||||
const CLASS_NAME_FADE = 'fade';
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Alert extends BaseComponent {
|
||||
// Getters
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
close() {
|
||||
const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);
|
||||
if (closeEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._element.classList.remove(CLASS_NAME_SHOW);
|
||||
const isAnimated = this._element.classList.contains(CLASS_NAME_FADE);
|
||||
this._queueCallback(() => this._destroyElement(), this._element, isAnimated);
|
||||
}
|
||||
|
||||
// Private
|
||||
_destroyElement() {
|
||||
this._element.remove();
|
||||
EventHandler.trigger(this._element, EVENT_CLOSED);
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Alert.getOrCreateInstance(this);
|
||||
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
|
||||
*/
|
||||
|
||||
componentFunctions_js.enableDismissTrigger(Alert, 'close');
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Alert);
|
||||
|
||||
return Alert;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=alert.js.map
|
1
bootstrap/js/dist/alert.js.map
vendored
Normal file
1
bootstrap/js/dist/alert.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"alert.js","sources":["../src/alert.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n"],"names":["NAME","DATA_KEY","EVENT_KEY","EVENT_CLOSE","EVENT_CLOSED","CLASS_NAME_FADE","CLASS_NAME_SHOW","Alert","BaseComponent","close","closeEvent","EventHandler","trigger","_element","defaultPrevented","classList","remove","isAnimated","contains","_queueCallback","_destroyElement","dispose","jQueryInterface","config","each","data","getOrCreateInstance","undefined","startsWith","TypeError","enableDismissTrigger","defineJQueryPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAOA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,OAAO,CAAA;EACpB,MAAMC,QAAQ,GAAG,UAAU,CAAA;EAC3B,MAAMC,SAAS,GAAI,CAAGD,CAAAA,EAAAA,QAAS,CAAC,CAAA,CAAA;EAEhC,MAAME,WAAW,GAAI,CAAOD,KAAAA,EAAAA,SAAU,CAAC,CAAA,CAAA;EACvC,MAAME,YAAY,GAAI,CAAQF,MAAAA,EAAAA,SAAU,CAAC,CAAA,CAAA;EACzC,MAAMG,eAAe,GAAG,MAAM,CAAA;EAC9B,MAAMC,eAAe,GAAG,MAAM,CAAA;;EAE9B;EACA;EACA;;EAEA,MAAMC,KAAK,SAASC,aAAa,CAAC;EAChC;IACA,WAAWR,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI,CAAA;EACb,GAAA;;EAEA;EACAS,EAAAA,KAAKA,GAAG;MACN,MAAMC,UAAU,GAAGC,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEV,WAAW,CAAC,CAAA;MAEnE,IAAIO,UAAU,CAACI,gBAAgB,EAAE;EAC/B,MAAA,OAAA;EACF,KAAA;MAEA,IAAI,CAACD,QAAQ,CAACE,SAAS,CAACC,MAAM,CAACV,eAAe,CAAC,CAAA;MAE/C,MAAMW,UAAU,GAAG,IAAI,CAACJ,QAAQ,CAACE,SAAS,CAACG,QAAQ,CAACb,eAAe,CAAC,CAAA;EACpE,IAAA,IAAI,CAACc,cAAc,CAAC,MAAM,IAAI,CAACC,eAAe,EAAE,EAAE,IAAI,CAACP,QAAQ,EAAEI,UAAU,CAAC,CAAA;EAC9E,GAAA;;EAEA;EACAG,EAAAA,eAAeA,GAAG;EAChB,IAAA,IAAI,CAACP,QAAQ,CAACG,MAAM,EAAE,CAAA;MACtBL,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAET,YAAY,CAAC,CAAA;MACjD,IAAI,CAACiB,OAAO,EAAE,CAAA;EAChB,GAAA;;EAEA;IACA,OAAOC,eAAeA,CAACC,MAAM,EAAE;EAC7B,IAAA,OAAO,IAAI,CAACC,IAAI,CAAC,YAAY;EAC3B,MAAA,MAAMC,IAAI,GAAGlB,KAAK,CAACmB,mBAAmB,CAAC,IAAI,CAAC,CAAA;EAE5C,MAAA,IAAI,OAAOH,MAAM,KAAK,QAAQ,EAAE;EAC9B,QAAA,OAAA;EACF,OAAA;EAEA,MAAA,IAAIE,IAAI,CAACF,MAAM,CAAC,KAAKI,SAAS,IAAIJ,MAAM,CAACK,UAAU,CAAC,GAAG,CAAC,IAAIL,MAAM,KAAK,aAAa,EAAE;EACpF,QAAA,MAAM,IAAIM,SAAS,CAAE,CAAmBN,iBAAAA,EAAAA,MAAO,GAAE,CAAC,CAAA;EACpD,OAAA;EAEAE,MAAAA,IAAI,CAACF,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;EACpB,KAAC,CAAC,CAAA;EACJ,GAAA;EACF,CAAA;;EAEA;EACA;EACA;;AAEAO,4CAAoB,CAACvB,KAAK,EAAE,OAAO,CAAC,CAAA;;EAEpC;EACA;EACA;;AAEAwB,6BAAkB,CAACxB,KAAK,CAAC;;;;;;;;"}
|
84
bootstrap/js/dist/base-component.js
vendored
Normal file
84
bootstrap/js/dist/base-component.js
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*!
|
||||
* Bootstrap base-component.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./dom/data.js'), require('./dom/event-handler.js'), require('./util/config.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./dom/data', './dom/event-handler', './util/config', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BaseComponent = factory(global.Data, global.EventHandler, global.Config, global.Index));
|
||||
})(this, (function (Data, EventHandler, Config, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap base-component.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const VERSION = '5.3.3';
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class BaseComponent extends Config {
|
||||
constructor(element, config) {
|
||||
super();
|
||||
element = index_js.getElement(element);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
this._element = element;
|
||||
this._config = this._getConfig(config);
|
||||
Data.set(this._element, this.constructor.DATA_KEY, this);
|
||||
}
|
||||
|
||||
// Public
|
||||
dispose() {
|
||||
Data.remove(this._element, this.constructor.DATA_KEY);
|
||||
EventHandler.off(this._element, this.constructor.EVENT_KEY);
|
||||
for (const propertyName of Object.getOwnPropertyNames(this)) {
|
||||
this[propertyName] = null;
|
||||
}
|
||||
}
|
||||
_queueCallback(callback, element, isAnimated = true) {
|
||||
index_js.executeAfterTransition(callback, element, isAnimated);
|
||||
}
|
||||
_getConfig(config) {
|
||||
config = this._mergeConfigObj(config, this._element);
|
||||
config = this._configAfterMerge(config);
|
||||
this._typeCheckConfig(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
// Static
|
||||
static getInstance(element) {
|
||||
return Data.get(index_js.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 DATA_KEY() {
|
||||
return `bs.${this.NAME}`;
|
||||
}
|
||||
static get EVENT_KEY() {
|
||||
return `.${this.DATA_KEY}`;
|
||||
}
|
||||
static eventName(name) {
|
||||
return `${name}${this.EVENT_KEY}`;
|
||||
}
|
||||
}
|
||||
|
||||
return BaseComponent;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=base-component.js.map
|
1
bootstrap/js/dist/base-component.js.map
vendored
Normal file
1
bootstrap/js/dist/base-component.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"base-component.js","sources":["../src/base-component.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Data from './dom/data.js'\nimport EventHandler from './dom/event-handler.js'\nimport Config from './util/config.js'\nimport { executeAfterTransition, getElement } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3'\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super()\n\n element = getElement(element)\n if (!element) {\n return\n }\n\n this._element = element\n this._config = this._getConfig(config)\n\n Data.set(this._element, this.constructor.DATA_KEY, this)\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY)\n EventHandler.off(this._element, this.constructor.EVENT_KEY)\n\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null\n }\n }\n\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated)\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY)\n }\n\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null)\n }\n\n static get VERSION() {\n return VERSION\n }\n\n static get DATA_KEY() {\n return `bs.${this.NAME}`\n }\n\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`\n }\n\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`\n }\n}\n\nexport default BaseComponent\n"],"names":["VERSION","BaseComponent","Config","constructor","element","config","getElement","_element","_config","_getConfig","Data","set","DATA_KEY","dispose","remove","EventHandler","off","EVENT_KEY","propertyName","Object","getOwnPropertyNames","_queueCallback","callback","isAnimated","executeAfterTransition","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","getInstance","get","getOrCreateInstance","NAME","eventName","name"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAOA;EACA;EACA;;EAEA,MAAMA,OAAO,GAAG,OAAO,CAAA;;EAEvB;EACA;EACA;;EAEA,MAAMC,aAAa,SAASC,MAAM,CAAC;EACjCC,EAAAA,WAAWA,CAACC,OAAO,EAAEC,MAAM,EAAE;EAC3B,IAAA,KAAK,EAAE,CAAA;EAEPD,IAAAA,OAAO,GAAGE,mBAAU,CAACF,OAAO,CAAC,CAAA;MAC7B,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAA;EACF,KAAA;MAEA,IAAI,CAACG,QAAQ,GAAGH,OAAO,CAAA;MACvB,IAAI,CAACI,OAAO,GAAG,IAAI,CAACC,UAAU,CAACJ,MAAM,CAAC,CAAA;EAEtCK,IAAAA,IAAI,CAACC,GAAG,CAAC,IAAI,CAACJ,QAAQ,EAAE,IAAI,CAACJ,WAAW,CAACS,QAAQ,EAAE,IAAI,CAAC,CAAA;EAC1D,GAAA;;EAEA;EACAC,EAAAA,OAAOA,GAAG;EACRH,IAAAA,IAAI,CAACI,MAAM,CAAC,IAAI,CAACP,QAAQ,EAAE,IAAI,CAACJ,WAAW,CAACS,QAAQ,CAAC,CAAA;EACrDG,IAAAA,YAAY,CAACC,GAAG,CAAC,IAAI,CAACT,QAAQ,EAAE,IAAI,CAACJ,WAAW,CAACc,SAAS,CAAC,CAAA;MAE3D,KAAK,MAAMC,YAAY,IAAIC,MAAM,CAACC,mBAAmB,CAAC,IAAI,CAAC,EAAE;EAC3D,MAAA,IAAI,CAACF,YAAY,CAAC,GAAG,IAAI,CAAA;EAC3B,KAAA;EACF,GAAA;IAEAG,cAAcA,CAACC,QAAQ,EAAElB,OAAO,EAAEmB,UAAU,GAAG,IAAI,EAAE;EACnDC,IAAAA,+BAAsB,CAACF,QAAQ,EAAElB,OAAO,EAAEmB,UAAU,CAAC,CAAA;EACvD,GAAA;IAEAd,UAAUA,CAACJ,MAAM,EAAE;MACjBA,MAAM,GAAG,IAAI,CAACoB,eAAe,CAACpB,MAAM,EAAE,IAAI,CAACE,QAAQ,CAAC,CAAA;EACpDF,IAAAA,MAAM,GAAG,IAAI,CAACqB,iBAAiB,CAACrB,MAAM,CAAC,CAAA;EACvC,IAAA,IAAI,CAACsB,gBAAgB,CAACtB,MAAM,CAAC,CAAA;EAC7B,IAAA,OAAOA,MAAM,CAAA;EACf,GAAA;;EAEA;IACA,OAAOuB,WAAWA,CAACxB,OAAO,EAAE;EAC1B,IAAA,OAAOM,IAAI,CAACmB,GAAG,CAACvB,mBAAU,CAACF,OAAO,CAAC,EAAE,IAAI,CAACQ,QAAQ,CAAC,CAAA;EACrD,GAAA;IAEA,OAAOkB,mBAAmBA,CAAC1B,OAAO,EAAEC,MAAM,GAAG,EAAE,EAAE;MAC/C,OAAO,IAAI,CAACuB,WAAW,CAACxB,OAAO,CAAC,IAAI,IAAI,IAAI,CAACA,OAAO,EAAE,OAAOC,MAAM,KAAK,QAAQ,GAAGA,MAAM,GAAG,IAAI,CAAC,CAAA;EACnG,GAAA;IAEA,WAAWL,OAAOA,GAAG;EACnB,IAAA,OAAOA,OAAO,CAAA;EAChB,GAAA;IAEA,WAAWY,QAAQA,GAAG;EACpB,IAAA,OAAQ,CAAK,GAAA,EAAA,IAAI,CAACmB,IAAK,CAAC,CAAA,CAAA;EAC1B,GAAA;IAEA,WAAWd,SAASA,GAAG;EACrB,IAAA,OAAQ,CAAG,CAAA,EAAA,IAAI,CAACL,QAAS,CAAC,CAAA,CAAA;EAC5B,GAAA;IAEA,OAAOoB,SAASA,CAACC,IAAI,EAAE;EACrB,IAAA,OAAQ,GAAEA,IAAK,CAAA,EAAE,IAAI,CAAChB,SAAU,CAAC,CAAA,CAAA;EACnC,GAAA;EACF;;;;;;;;"}
|
79
bootstrap/js/dist/button.js
vendored
Normal file
79
bootstrap/js/dist/button.js
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*!
|
||||
* Bootstrap button.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Button = factory(global.BaseComponent, global.EventHandler, global.Index));
|
||||
})(this, (function (BaseComponent, EventHandler, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap button.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'button';
|
||||
const DATA_KEY = 'bs.button';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const CLASS_NAME_ACTIVE = 'active';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="button"]';
|
||||
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Button extends BaseComponent {
|
||||
// Getters
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
// 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) {
|
||||
return this.each(function () {
|
||||
const data = Button.getOrCreateInstance(this);
|
||||
if (config === 'toggle') {
|
||||
data[config]();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {
|
||||
event.preventDefault();
|
||||
const button = event.target.closest(SELECTOR_DATA_TOGGLE);
|
||||
const data = Button.getOrCreateInstance(button);
|
||||
data.toggle();
|
||||
});
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Button);
|
||||
|
||||
return Button;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=button.js.map
|
1
bootstrap/js/dist/button.js.map
vendored
Normal file
1
bootstrap/js/dist/button.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"button.js","sources":["../src/button.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n"],"names":["NAME","DATA_KEY","EVENT_KEY","DATA_API_KEY","CLASS_NAME_ACTIVE","SELECTOR_DATA_TOGGLE","EVENT_CLICK_DATA_API","Button","BaseComponent","toggle","_element","setAttribute","classList","jQueryInterface","config","each","data","getOrCreateInstance","EventHandler","on","document","event","preventDefault","button","target","closest","defineJQueryPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAMA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,QAAQ,CAAA;EACrB,MAAMC,QAAQ,GAAG,WAAW,CAAA;EAC5B,MAAMC,SAAS,GAAI,CAAGD,CAAAA,EAAAA,QAAS,CAAC,CAAA,CAAA;EAChC,MAAME,YAAY,GAAG,WAAW,CAAA;EAEhC,MAAMC,iBAAiB,GAAG,QAAQ,CAAA;EAClC,MAAMC,oBAAoB,GAAG,2BAA2B,CAAA;EACxD,MAAMC,oBAAoB,GAAI,CAAA,KAAA,EAAOJ,SAAU,CAAA,EAAEC,YAAa,CAAC,CAAA,CAAA;;EAE/D;EACA;EACA;;EAEA,MAAMI,MAAM,SAASC,aAAa,CAAC;EACjC;IACA,WAAWR,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI,CAAA;EACb,GAAA;;EAEA;EACAS,EAAAA,MAAMA,GAAG;EACP;EACA,IAAA,IAAI,CAACC,QAAQ,CAACC,YAAY,CAAC,cAAc,EAAE,IAAI,CAACD,QAAQ,CAACE,SAAS,CAACH,MAAM,CAACL,iBAAiB,CAAC,CAAC,CAAA;EAC/F,GAAA;;EAEA;IACA,OAAOS,eAAeA,CAACC,MAAM,EAAE;EAC7B,IAAA,OAAO,IAAI,CAACC,IAAI,CAAC,YAAY;EAC3B,MAAA,MAAMC,IAAI,GAAGT,MAAM,CAACU,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAE7C,IAAIH,MAAM,KAAK,QAAQ,EAAE;EACvBE,QAAAA,IAAI,CAACF,MAAM,CAAC,EAAE,CAAA;EAChB,OAAA;EACF,KAAC,CAAC,CAAA;EACJ,GAAA;EACF,CAAA;;EAEA;EACA;EACA;;EAEAI,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEd,oBAAoB,EAAED,oBAAoB,EAAEgB,KAAK,IAAI;IAC7EA,KAAK,CAACC,cAAc,EAAE,CAAA;IAEtB,MAAMC,MAAM,GAAGF,KAAK,CAACG,MAAM,CAACC,OAAO,CAACpB,oBAAoB,CAAC,CAAA;EACzD,EAAA,MAAMW,IAAI,GAAGT,MAAM,CAACU,mBAAmB,CAACM,MAAM,CAAC,CAAA;IAE/CP,IAAI,CAACP,MAAM,EAAE,CAAA;EACf,CAAC,CAAC,CAAA;;EAEF;EACA;EACA;;AAEAiB,6BAAkB,CAACnB,MAAM,CAAC;;;;;;;;"}
|
388
bootstrap/js/dist/carousel.js
vendored
Normal file
388
bootstrap/js/dist/carousel.js
vendored
Normal file
|
@ -0,0 +1,388 @@
|
|||
/*!
|
||||
* Bootstrap carousel.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js'), require('./util/swipe.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index', './util/swipe'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Carousel = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index, global.Swipe));
|
||||
})(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js, Swipe) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap carousel.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'carousel';
|
||||
const DATA_KEY = 'bs.carousel';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
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 ORDER_NEXT = 'next';
|
||||
const ORDER_PREV = 'prev';
|
||||
const DIRECTION_LEFT = 'left';
|
||||
const DIRECTION_RIGHT = 'right';
|
||||
const EVENT_SLIDE = `slide${EVENT_KEY}`;
|
||||
const EVENT_SLID = `slid${EVENT_KEY}`;
|
||||
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;
|
||||
const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`;
|
||||
const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`;
|
||||
const EVENT_DRAG_START = `dragstart${EVENT_KEY}`;
|
||||
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`;
|
||||
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_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 SELECTOR_ACTIVE = '.active';
|
||||
const SELECTOR_ITEM = '.carousel-item';
|
||||
const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;
|
||||
const SELECTOR_ITEM_IMG = '.carousel-item img';
|
||||
const SELECTOR_INDICATORS = '.carousel-indicators';
|
||||
const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';
|
||||
const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]';
|
||||
const KEY_TO_DIRECTION = {
|
||||
[ARROW_LEFT_KEY]: DIRECTION_RIGHT,
|
||||
[ARROW_RIGHT_KEY]: DIRECTION_LEFT
|
||||
};
|
||||
const Default = {
|
||||
interval: 5000,
|
||||
keyboard: true,
|
||||
pause: 'hover',
|
||||
ride: false,
|
||||
touch: true,
|
||||
wrap: true
|
||||
};
|
||||
const DefaultType = {
|
||||
interval: '(number|boolean)',
|
||||
// TODO:v6 remove boolean support
|
||||
keyboard: 'boolean',
|
||||
pause: '(string|boolean)',
|
||||
ride: '(boolean|string)',
|
||||
touch: 'boolean',
|
||||
wrap: 'boolean'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Carousel extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._interval = null;
|
||||
this._activeElement = null;
|
||||
this._isSliding = false;
|
||||
this.touchTimeout = null;
|
||||
this._swipeHelper = null;
|
||||
this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);
|
||||
this._addEventListeners();
|
||||
if (this._config.ride === CLASS_NAME_CAROUSEL) {
|
||||
this.cycle();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
next() {
|
||||
this._slide(ORDER_NEXT);
|
||||
}
|
||||
nextWhenVisible() {
|
||||
// FIXME TODO use `document.visibilityState`
|
||||
// Don't call next when the page isn't visible
|
||||
// or the carousel or its parent isn't visible
|
||||
if (!document.hidden && index_js.isVisible(this._element)) {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
prev() {
|
||||
this._slide(ORDER_PREV);
|
||||
}
|
||||
pause() {
|
||||
if (this._isSliding) {
|
||||
index_js.triggerTransitionEnd(this._element);
|
||||
}
|
||||
this._clearInterval();
|
||||
}
|
||||
cycle() {
|
||||
this._clearInterval();
|
||||
this._updateInterval();
|
||||
this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);
|
||||
}
|
||||
_maybeEnableCycle() {
|
||||
if (!this._config.ride) {
|
||||
return;
|
||||
}
|
||||
if (this._isSliding) {
|
||||
EventHandler.one(this._element, EVENT_SLID, () => this.cycle());
|
||||
return;
|
||||
}
|
||||
this.cycle();
|
||||
}
|
||||
to(index) {
|
||||
const items = this._getItems();
|
||||
if (index > items.length - 1 || index < 0) {
|
||||
return;
|
||||
}
|
||||
if (this._isSliding) {
|
||||
EventHandler.one(this._element, EVENT_SLID, () => this.to(index));
|
||||
return;
|
||||
}
|
||||
const activeIndex = this._getItemIndex(this._getActive());
|
||||
if (activeIndex === index) {
|
||||
return;
|
||||
}
|
||||
const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;
|
||||
this._slide(order, items[index]);
|
||||
}
|
||||
dispose() {
|
||||
if (this._swipeHelper) {
|
||||
this._swipeHelper.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config) {
|
||||
config.defaultInterval = config.interval;
|
||||
return config;
|
||||
}
|
||||
_addEventListeners() {
|
||||
if (this._config.keyboard) {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));
|
||||
}
|
||||
if (this._config.pause === 'hover') {
|
||||
EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause());
|
||||
EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle());
|
||||
}
|
||||
if (this._config.touch && Swipe.isSupported()) {
|
||||
this._addTouchEventListeners();
|
||||
}
|
||||
}
|
||||
_addTouchEventListeners() {
|
||||
for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {
|
||||
EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());
|
||||
}
|
||||
const endCallBack = () => {
|
||||
if (this._config.pause !== 'hover') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a touch-enabled device, mouseenter/leave are fired as
|
||||
// part of the mouse compatibility events on first tap - the carousel
|
||||
// would stop cycling until user tapped out of it;
|
||||
// here, we listen for touchend, explicitly pause the carousel
|
||||
// (as if it's the second time we tap on it, mouseenter compat event
|
||||
// is NOT fired) and after a timeout (to allow for mouse compatibility
|
||||
// events to fire) we explicitly restart cycling
|
||||
|
||||
this.pause();
|
||||
if (this.touchTimeout) {
|
||||
clearTimeout(this.touchTimeout);
|
||||
}
|
||||
this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);
|
||||
};
|
||||
const swipeConfig = {
|
||||
leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),
|
||||
rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),
|
||||
endCallback: endCallBack
|
||||
};
|
||||
this._swipeHelper = new Swipe(this._element, swipeConfig);
|
||||
}
|
||||
_keydown(event) {
|
||||
if (/input|textarea/i.test(event.target.tagName)) {
|
||||
return;
|
||||
}
|
||||
const direction = KEY_TO_DIRECTION[event.key];
|
||||
if (direction) {
|
||||
event.preventDefault();
|
||||
this._slide(this._directionToOrder(direction));
|
||||
}
|
||||
}
|
||||
_getItemIndex(element) {
|
||||
return this._getItems().indexOf(element);
|
||||
}
|
||||
_setActiveIndicatorElement(index) {
|
||||
if (!this._indicatorsElement) {
|
||||
return;
|
||||
}
|
||||
const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);
|
||||
activeIndicator.classList.remove(CLASS_NAME_ACTIVE);
|
||||
activeIndicator.removeAttribute('aria-current');
|
||||
const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement);
|
||||
if (newActiveIndicator) {
|
||||
newActiveIndicator.classList.add(CLASS_NAME_ACTIVE);
|
||||
newActiveIndicator.setAttribute('aria-current', 'true');
|
||||
}
|
||||
}
|
||||
_updateInterval() {
|
||||
const element = this._activeElement || this._getActive();
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);
|
||||
this._config.interval = elementInterval || this._config.defaultInterval;
|
||||
}
|
||||
_slide(order, element = null) {
|
||||
if (this._isSliding) {
|
||||
return;
|
||||
}
|
||||
const activeElement = this._getActive();
|
||||
const isNext = order === ORDER_NEXT;
|
||||
const nextElement = element || index_js.getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);
|
||||
if (nextElement === activeElement) {
|
||||
return;
|
||||
}
|
||||
const nextElementIndex = this._getItemIndex(nextElement);
|
||||
const triggerEvent = eventName => {
|
||||
return EventHandler.trigger(this._element, eventName, {
|
||||
relatedTarget: nextElement,
|
||||
direction: this._orderToDirection(order),
|
||||
from: this._getItemIndex(activeElement),
|
||||
to: nextElementIndex
|
||||
});
|
||||
};
|
||||
const slideEvent = triggerEvent(EVENT_SLIDE);
|
||||
if (slideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
if (!activeElement || !nextElement) {
|
||||
// Some weirdness is happening, so we bail
|
||||
// TODO: change tests that use empty divs to avoid this check
|
||||
return;
|
||||
}
|
||||
const isCycling = Boolean(this._interval);
|
||||
this.pause();
|
||||
this._isSliding = true;
|
||||
this._setActiveIndicatorElement(nextElementIndex);
|
||||
this._activeElement = nextElement;
|
||||
const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;
|
||||
const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;
|
||||
nextElement.classList.add(orderClassName);
|
||||
index_js.reflow(nextElement);
|
||||
activeElement.classList.add(directionalClassName);
|
||||
nextElement.classList.add(directionalClassName);
|
||||
const completeCallBack = () => {
|
||||
nextElement.classList.remove(directionalClassName, orderClassName);
|
||||
nextElement.classList.add(CLASS_NAME_ACTIVE);
|
||||
activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName);
|
||||
this._isSliding = false;
|
||||
triggerEvent(EVENT_SLID);
|
||||
};
|
||||
this._queueCallback(completeCallBack, activeElement, this._isAnimated());
|
||||
if (isCycling) {
|
||||
this.cycle();
|
||||
}
|
||||
}
|
||||
_isAnimated() {
|
||||
return this._element.classList.contains(CLASS_NAME_SLIDE);
|
||||
}
|
||||
_getActive() {
|
||||
return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);
|
||||
}
|
||||
_getItems() {
|
||||
return SelectorEngine.find(SELECTOR_ITEM, this._element);
|
||||
}
|
||||
_clearInterval() {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = null;
|
||||
}
|
||||
}
|
||||
_directionToOrder(direction) {
|
||||
if (index_js.isRTL()) {
|
||||
return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;
|
||||
}
|
||||
return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;
|
||||
}
|
||||
_orderToDirection(order) {
|
||||
if (index_js.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 () {
|
||||
const data = Carousel.getOrCreateInstance(this, config);
|
||||
if (typeof config === 'number') {
|
||||
data.to(config);
|
||||
return;
|
||||
}
|
||||
if (typeof config === 'string') {
|
||||
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config]();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
|
||||
const target = SelectorEngine.getElementFromSelector(this);
|
||||
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const carousel = Carousel.getOrCreateInstance(target);
|
||||
const slideIndex = this.getAttribute('data-bs-slide-to');
|
||||
if (slideIndex) {
|
||||
carousel.to(slideIndex);
|
||||
carousel._maybeEnableCycle();
|
||||
return;
|
||||
}
|
||||
if (Manipulator.getDataAttribute(this, 'slide') === 'next') {
|
||||
carousel.next();
|
||||
carousel._maybeEnableCycle();
|
||||
return;
|
||||
}
|
||||
carousel.prev();
|
||||
carousel._maybeEnableCycle();
|
||||
});
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);
|
||||
for (const carousel of carousels) {
|
||||
Carousel.getOrCreateInstance(carousel);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Carousel);
|
||||
|
||||
return Carousel;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=carousel.js.map
|
1
bootstrap/js/dist/carousel.js.map
vendored
Normal file
1
bootstrap/js/dist/carousel.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
249
bootstrap/js/dist/collapse.js
vendored
Normal file
249
bootstrap/js/dist/collapse.js
vendored
Normal file
|
@ -0,0 +1,249 @@
|
|||
/*!
|
||||
* Bootstrap collapse.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Collapse = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap collapse.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'collapse';
|
||||
const DATA_KEY = 'bs.collapse';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
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 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 WIDTH = 'width';
|
||||
const HEIGHT = 'height';
|
||||
const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="collapse"]';
|
||||
const Default = {
|
||||
parent: null,
|
||||
toggle: true
|
||||
};
|
||||
const DefaultType = {
|
||||
parent: '(null|element)',
|
||||
toggle: 'boolean'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Collapse extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._isTransitioning = false;
|
||||
this._triggerArray = [];
|
||||
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE);
|
||||
for (const elem of toggleList) {
|
||||
const selector = SelectorEngine.getSelectorFromElement(elem);
|
||||
const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);
|
||||
if (selector !== null && filterElement.length) {
|
||||
this._triggerArray.push(elem);
|
||||
}
|
||||
}
|
||||
this._initializeChildren();
|
||||
if (!this._config.parent) {
|
||||
this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());
|
||||
}
|
||||
if (this._config.toggle) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
if (this._isShown()) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
show() {
|
||||
if (this._isTransitioning || this._isShown()) {
|
||||
return;
|
||||
}
|
||||
let activeChildren = [];
|
||||
|
||||
// find active children
|
||||
if (this._config.parent) {
|
||||
activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {
|
||||
toggle: false
|
||||
}));
|
||||
}
|
||||
if (activeChildren.length && activeChildren[0]._isTransitioning) {
|
||||
return;
|
||||
}
|
||||
const startEvent = EventHandler.trigger(this._element, EVENT_SHOW);
|
||||
if (startEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
for (const activeInstance of activeChildren) {
|
||||
activeInstance.hide();
|
||||
}
|
||||
const dimension = this._getDimension();
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSE);
|
||||
this._element.classList.add(CLASS_NAME_COLLAPSING);
|
||||
this._element.style[dimension] = 0;
|
||||
this._addAriaAndCollapsedClass(this._triggerArray, true);
|
||||
this._isTransitioning = true;
|
||||
const complete = () => {
|
||||
this._isTransitioning = false;
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSING);
|
||||
this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW);
|
||||
this._element.style[dimension] = '';
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN);
|
||||
};
|
||||
const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);
|
||||
const scrollSize = `scroll${capitalizedDimension}`;
|
||||
this._queueCallback(complete, this._element, true);
|
||||
this._element.style[dimension] = `${this._element[scrollSize]}px`;
|
||||
}
|
||||
hide() {
|
||||
if (this._isTransitioning || !this._isShown()) {
|
||||
return;
|
||||
}
|
||||
const startEvent = EventHandler.trigger(this._element, EVENT_HIDE);
|
||||
if (startEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
const dimension = this._getDimension();
|
||||
this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;
|
||||
index_js.reflow(this._element);
|
||||
this._element.classList.add(CLASS_NAME_COLLAPSING);
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW);
|
||||
for (const trigger of this._triggerArray) {
|
||||
const element = SelectorEngine.getElementFromSelector(trigger);
|
||||
if (element && !this._isShown(element)) {
|
||||
this._addAriaAndCollapsedClass([trigger], false);
|
||||
}
|
||||
}
|
||||
this._isTransitioning = true;
|
||||
const complete = () => {
|
||||
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] = '';
|
||||
this._queueCallback(complete, this._element, true);
|
||||
}
|
||||
_isShown(element = this._element) {
|
||||
return element.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config) {
|
||||
config.toggle = Boolean(config.toggle); // Coerce string values
|
||||
config.parent = index_js.getElement(config.parent);
|
||||
return config;
|
||||
}
|
||||
_getDimension() {
|
||||
return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;
|
||||
}
|
||||
_initializeChildren() {
|
||||
if (!this._config.parent) {
|
||||
return;
|
||||
}
|
||||
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE);
|
||||
for (const element of children) {
|
||||
const selected = SelectorEngine.getElementFromSelector(element);
|
||||
if (selected) {
|
||||
this._addAriaAndCollapsedClass([element], this._isShown(selected));
|
||||
}
|
||||
}
|
||||
}
|
||||
_getFirstLevelChildren(selector) {
|
||||
const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);
|
||||
// remove children if greater depth
|
||||
return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));
|
||||
}
|
||||
_addAriaAndCollapsedClass(triggerArray, isOpen) {
|
||||
if (!triggerArray.length) {
|
||||
return;
|
||||
}
|
||||
for (const element of triggerArray) {
|
||||
element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);
|
||||
element.setAttribute('aria-expanded', isOpen);
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
const _config = {};
|
||||
if (typeof config === 'string' && /show|hide/.test(config)) {
|
||||
_config.toggle = false;
|
||||
}
|
||||
return this.each(function () {
|
||||
const data = Collapse.getOrCreateInstance(this, _config);
|
||||
if (typeof config === 'string') {
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config]();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
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.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {
|
||||
event.preventDefault();
|
||||
}
|
||||
for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {
|
||||
Collapse.getOrCreateInstance(element, {
|
||||
toggle: false
|
||||
}).toggle();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Collapse);
|
||||
|
||||
return Collapse;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=collapse.js.map
|
1
bootstrap/js/dist/collapse.js.map
vendored
Normal file
1
bootstrap/js/dist/collapse.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
63
bootstrap/js/dist/dom/data.js
vendored
Normal file
63
bootstrap/js/dist/dom/data.js
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*!
|
||||
* Bootstrap data.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Data = factory());
|
||||
})(this, (function () { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/data.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const elementMap = new Map();
|
||||
const data = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=data.js.map
|
1
bootstrap/js/dist/dom/data.js.map
vendored
Normal file
1
bootstrap/js/dist/dom/data.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"data.js","sources":["../../src/dom/data.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map()\n\nexport default {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map())\n }\n\n const instanceMap = elementMap.get(element)\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)\n return\n }\n\n instanceMap.set(key, instance)\n },\n\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null\n }\n\n return null\n },\n\n remove(element, key) {\n if (!elementMap.has(element)) {\n return\n }\n\n const instanceMap = elementMap.get(element)\n\n instanceMap.delete(key)\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element)\n }\n }\n}\n"],"names":["elementMap","Map","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;;EAEA,MAAMA,UAAU,GAAG,IAAIC,GAAG,EAAE,CAAA;AAE5B,eAAe;EACbC,EAAAA,GAAGA,CAACC,OAAO,EAAEC,GAAG,EAAEC,QAAQ,EAAE;EAC1B,IAAA,IAAI,CAACL,UAAU,CAACM,GAAG,CAACH,OAAO,CAAC,EAAE;QAC5BH,UAAU,CAACE,GAAG,CAACC,OAAO,EAAE,IAAIF,GAAG,EAAE,CAAC,CAAA;EACpC,KAAA;EAEA,IAAA,MAAMM,WAAW,GAAGP,UAAU,CAACQ,GAAG,CAACL,OAAO,CAAC,CAAA;;EAE3C;EACA;EACA,IAAA,IAAI,CAACI,WAAW,CAACD,GAAG,CAACF,GAAG,CAAC,IAAIG,WAAW,CAACE,IAAI,KAAK,CAAC,EAAE;EACnD;EACAC,MAAAA,OAAO,CAACC,KAAK,CAAE,+EAA8EC,KAAK,CAACC,IAAI,CAACN,WAAW,CAACO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAE,GAAE,CAAC,CAAA;EAClI,MAAA,OAAA;EACF,KAAA;EAEAP,IAAAA,WAAW,CAACL,GAAG,CAACE,GAAG,EAAEC,QAAQ,CAAC,CAAA;KAC/B;EAEDG,EAAAA,GAAGA,CAACL,OAAO,EAAEC,GAAG,EAAE;EAChB,IAAA,IAAIJ,UAAU,CAACM,GAAG,CAACH,OAAO,CAAC,EAAE;EAC3B,MAAA,OAAOH,UAAU,CAACQ,GAAG,CAACL,OAAO,CAAC,CAACK,GAAG,CAACJ,GAAG,CAAC,IAAI,IAAI,CAAA;EACjD,KAAA;EAEA,IAAA,OAAO,IAAI,CAAA;KACZ;EAEDW,EAAAA,MAAMA,CAACZ,OAAO,EAAEC,GAAG,EAAE;EACnB,IAAA,IAAI,CAACJ,UAAU,CAACM,GAAG,CAACH,OAAO,CAAC,EAAE;EAC5B,MAAA,OAAA;EACF,KAAA;EAEA,IAAA,MAAMI,WAAW,GAAGP,UAAU,CAACQ,GAAG,CAACL,OAAO,CAAC,CAAA;EAE3CI,IAAAA,WAAW,CAACS,MAAM,CAACZ,GAAG,CAAC,CAAA;;EAEvB;EACA,IAAA,IAAIG,WAAW,CAACE,IAAI,KAAK,CAAC,EAAE;EAC1BT,MAAAA,UAAU,CAACgB,MAAM,CAACb,OAAO,CAAC,CAAA;EAC5B,KAAA;EACF,GAAA;EACF,CAAC;;;;;;;;"}
|
237
bootstrap/js/dist/dom/event-handler.js
vendored
Normal file
237
bootstrap/js/dist/dom/event-handler.js
vendored
Normal file
|
@ -0,0 +1,237 @@
|
|||
/*!
|
||||
* Bootstrap event-handler.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EventHandler = factory(global.Index));
|
||||
})(this, (function (index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/event-handler.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const namespaceRegex = /[^.]*(?=\..*)\.|.*/;
|
||||
const stripNameRegex = /\..*/;
|
||||
const stripUidRegex = /::\d+$/;
|
||||
const eventRegistry = {}; // Events storage
|
||||
let uidEvent = 1;
|
||||
const customEvents = {
|
||||
mouseenter: 'mouseover',
|
||||
mouseleave: 'mouseout'
|
||||
};
|
||||
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 makeEventUid(element, uid) {
|
||||
return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;
|
||||
}
|
||||
function getElementEvents(element) {
|
||||
const uid = makeEventUid(element);
|
||||
element.uidEvent = uid;
|
||||
eventRegistry[uid] = eventRegistry[uid] || {};
|
||||
return eventRegistry[uid];
|
||||
}
|
||||
function bootstrapHandler(element, fn) {
|
||||
return function handler(event) {
|
||||
hydrateObj(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 (const domElement of domElements) {
|
||||
if (domElement !== target) {
|
||||
continue;
|
||||
}
|
||||
hydrateObj(event, {
|
||||
delegateTarget: target
|
||||
});
|
||||
if (handler.oneOff) {
|
||||
EventHandler.off(element, event.type, selector, fn);
|
||||
}
|
||||
return fn.apply(target, [event]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function findHandler(events, callable, delegationSelector = null) {
|
||||
return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);
|
||||
}
|
||||
function normalizeParameters(originalTypeEvent, handler, delegationFunction) {
|
||||
const isDelegated = typeof handler === 'string';
|
||||
// TODO: tooltip passes `false` instead of selector, so we need to check
|
||||
const callable = isDelegated ? delegationFunction : handler || delegationFunction;
|
||||
let typeEvent = getTypeEvent(originalTypeEvent);
|
||||
if (!nativeEvents.has(typeEvent)) {
|
||||
typeEvent = originalTypeEvent;
|
||||
}
|
||||
return [isDelegated, callable, typeEvent];
|
||||
}
|
||||
function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {
|
||||
if (typeof originalTypeEvent !== 'string' || !element) {
|
||||
return;
|
||||
}
|
||||
let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);
|
||||
|
||||
// in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position
|
||||
// this prevents the handler from being dispatched the same way as mouseover or mouseout does
|
||||
if (originalTypeEvent in customEvents) {
|
||||
const wrapFunction = fn => {
|
||||
return function (event) {
|
||||
if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {
|
||||
return fn.call(this, event);
|
||||
}
|
||||
};
|
||||
};
|
||||
callable = wrapFunction(callable);
|
||||
}
|
||||
const events = getElementEvents(element);
|
||||
const handlers = events[typeEvent] || (events[typeEvent] = {});
|
||||
const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);
|
||||
if (previousFunction) {
|
||||
previousFunction.oneOff = previousFunction.oneOff && oneOff;
|
||||
return;
|
||||
}
|
||||
const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));
|
||||
const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);
|
||||
fn.delegationSelector = isDelegated ? handler : null;
|
||||
fn.callable = callable;
|
||||
fn.oneOff = oneOff;
|
||||
fn.uidEvent = uid;
|
||||
handlers[uid] = fn;
|
||||
element.addEventListener(typeEvent, fn, isDelegated);
|
||||
}
|
||||
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] || {};
|
||||
for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
|
||||
if (handlerKey.includes(namespace)) {
|
||||
removeHandler(element, events, typeEvent, event.callable, 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, delegationFunction) {
|
||||
addHandler(element, event, handler, delegationFunction, false);
|
||||
},
|
||||
one(element, event, handler, delegationFunction) {
|
||||
addHandler(element, event, handler, delegationFunction, true);
|
||||
},
|
||||
off(element, originalTypeEvent, handler, delegationFunction) {
|
||||
if (typeof originalTypeEvent !== 'string' || !element) {
|
||||
return;
|
||||
}
|
||||
const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);
|
||||
const inNamespace = typeEvent !== originalTypeEvent;
|
||||
const events = getElementEvents(element);
|
||||
const storeElementEvent = events[typeEvent] || {};
|
||||
const isNamespace = originalTypeEvent.startsWith('.');
|
||||
if (typeof callable !== 'undefined') {
|
||||
// Simplest case: handler is passed, remove that listener ONLY.
|
||||
if (!Object.keys(storeElementEvent).length) {
|
||||
return;
|
||||
}
|
||||
removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);
|
||||
return;
|
||||
}
|
||||
if (isNamespace) {
|
||||
for (const elementEvent of Object.keys(events)) {
|
||||
removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));
|
||||
}
|
||||
}
|
||||
for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {
|
||||
const handlerKey = keyHandlers.replace(stripUidRegex, '');
|
||||
if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
|
||||
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);
|
||||
}
|
||||
}
|
||||
},
|
||||
trigger(element, event, args) {
|
||||
if (typeof event !== 'string' || !element) {
|
||||
return null;
|
||||
}
|
||||
const $ = index_js.getjQuery();
|
||||
const typeEvent = getTypeEvent(event);
|
||||
const inNamespace = event !== typeEvent;
|
||||
let jQueryEvent = null;
|
||||
let bubbles = true;
|
||||
let nativeDispatch = true;
|
||||
let defaultPrevented = false;
|
||||
if (inNamespace && $) {
|
||||
jQueryEvent = $.Event(event, args);
|
||||
$(element).trigger(jQueryEvent);
|
||||
bubbles = !jQueryEvent.isPropagationStopped();
|
||||
nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();
|
||||
defaultPrevented = jQueryEvent.isDefaultPrevented();
|
||||
}
|
||||
const evt = hydrateObj(new Event(event, {
|
||||
bubbles,
|
||||
cancelable: true
|
||||
}), args);
|
||||
if (defaultPrevented) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (nativeDispatch) {
|
||||
element.dispatchEvent(evt);
|
||||
}
|
||||
if (evt.defaultPrevented && jQueryEvent) {
|
||||
jQueryEvent.preventDefault();
|
||||
}
|
||||
return evt;
|
||||
}
|
||||
};
|
||||
function hydrateObj(obj, meta = {}) {
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
try {
|
||||
obj[key] = value;
|
||||
} catch (_unused) {
|
||||
Object.defineProperty(obj, key, {
|
||||
configurable: true,
|
||||
get() {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
return EventHandler;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=event-handler.js.map
|
1
bootstrap/js/dist/dom/event-handler.js.map
vendored
Normal file
1
bootstrap/js/dist/dom/event-handler.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
72
bootstrap/js/dist/dom/manipulator.js
vendored
Normal file
72
bootstrap/js/dist/dom/manipulator.js
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*!
|
||||
* Bootstrap manipulator.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Manipulator = factory());
|
||||
})(this, (function () { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/manipulator.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
function normalizeData(value) {
|
||||
if (value === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value === 'false') {
|
||||
return false;
|
||||
}
|
||||
if (value === Number(value).toString()) {
|
||||
return Number(value);
|
||||
}
|
||||
if (value === '' || value === 'null') {
|
||||
return null;
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(value));
|
||||
} catch (_unused) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
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 = {};
|
||||
const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));
|
||||
for (const key of bsKeys) {
|
||||
let pureKey = key.replace(/^bs/, '');
|
||||
pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);
|
||||
attributes[pureKey] = normalizeData(element.dataset[key]);
|
||||
}
|
||||
return attributes;
|
||||
},
|
||||
getDataAttribute(element, key) {
|
||||
return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));
|
||||
}
|
||||
};
|
||||
|
||||
return Manipulator;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=manipulator.js.map
|
1
bootstrap/js/dist/dom/manipulator.js.map
vendored
Normal file
1
bootstrap/js/dist/dom/manipulator.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","_unused","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","length","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI,CAAA;EACb,GAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK,CAAA;EACd,GAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC,CAAA;EACtB,GAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI,CAAA;EACb,GAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK,CAAA;EACd,GAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC,CAAA;KAC7C,CAAC,OAAAM,OAAA,EAAM;EACN,IAAA,OAAON,KAAK,CAAA;EACd,GAAA;EACF,CAAA;EAEA,SAASO,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAK,CAAA,CAAA,EAAGA,GAAG,CAACC,WAAW,EAAG,EAAC,CAAC,CAAA;EAC9D,CAAA;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAER,KAAK,EAAE;MACpCc,OAAO,CAACC,YAAY,CAAE,CAAUR,QAAAA,EAAAA,gBAAgB,CAACC,GAAG,CAAE,CAAA,CAAC,EAAER,KAAK,CAAC,CAAA;KAChE;EAEDgB,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAE,CAAA,QAAA,EAAUV,gBAAgB,CAACC,GAAG,CAAE,CAAA,CAAC,CAAC,CAAA;KAC5D;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE,CAAA;EACX,KAAA;MAEA,MAAMK,UAAU,GAAG,EAAE,CAAA;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC,CAAA;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QACpCiB,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,EAAEF,OAAO,CAACG,MAAM,CAAC,CAAA;EAC5EV,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG3B,aAAa,CAACe,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC,CAAA;EAC3D,KAAA;EAEA,IAAA,OAAOW,UAAU,CAAA;KAClB;EAEDW,EAAAA,gBAAgBA,CAAChB,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOT,aAAa,CAACe,OAAO,CAACiB,YAAY,CAAE,CAAUxB,QAAAA,EAAAA,gBAAgB,CAACC,GAAG,CAAE,CAAA,CAAC,CAAC,CAAC,CAAA;EAChF,GAAA;EACF;;;;;;;;"}
|
104
bootstrap/js/dist/dom/selector-engine.js
vendored
Normal file
104
bootstrap/js/dist/dom/selector-engine.js
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*!
|
||||
* Bootstrap selector-engine.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SelectorEngine = factory(global.Index));
|
||||
})(this, (function (index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/selector-engine.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
const getSelector = element => {
|
||||
let selector = element.getAttribute('data-bs-target');
|
||||
if (!selector || selector === '#') {
|
||||
let hrefAttribute = element.getAttribute('href');
|
||||
|
||||
// The only valid content that could double as a selector are IDs or classes,
|
||||
// so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
||||
// `document.querySelector` will rightfully complain it is invalid.
|
||||
// See https://github.com/twbs/bootstrap/issues/32273
|
||||
if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Just in case some CMS puts out a full URL with the anchor appended
|
||||
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
||||
hrefAttribute = `#${hrefAttribute.split('#')[1]}`;
|
||||
}
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;
|
||||
}
|
||||
return selector ? selector.split(',').map(sel => index_js.parseSelector(sel)).join(',') : null;
|
||||
};
|
||||
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.closest(selector);
|
||||
while (ancestor) {
|
||||
parents.push(ancestor);
|
||||
ancestor = ancestor.parentNode.closest(selector);
|
||||
}
|
||||
return parents;
|
||||
},
|
||||
prev(element, selector) {
|
||||
let previous = element.previousElementSibling;
|
||||
while (previous) {
|
||||
if (previous.matches(selector)) {
|
||||
return [previous];
|
||||
}
|
||||
previous = previous.previousElementSibling;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
// TODO: this is now unused; remove later along with prev()
|
||||
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 => !index_js.isDisabled(el) && index_js.isVisible(el));
|
||||
},
|
||||
getSelectorFromElement(element) {
|
||||
const selector = getSelector(element);
|
||||
if (selector) {
|
||||
return SelectorEngine.findOne(selector) ? selector : null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getElementFromSelector(element) {
|
||||
const selector = getSelector(element);
|
||||
return selector ? SelectorEngine.findOne(selector) : null;
|
||||
},
|
||||
getMultipleElementsFromSelector(element) {
|
||||
const selector = getSelector(element);
|
||||
return selector ? SelectorEngine.find(selector) : [];
|
||||
}
|
||||
};
|
||||
|
||||
return SelectorEngine;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=selector-engine.js.map
|
1
bootstrap/js/dist/dom/selector-engine.js.map
vendored
Normal file
1
bootstrap/js/dist/dom/selector-engine.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
402
bootstrap/js/dist/dropdown.js
vendored
Normal file
402
bootstrap/js/dist/dropdown.js
vendored
Normal file
|
@ -0,0 +1,402 @@
|
|||
/*!
|
||||
* Bootstrap dropdown.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));
|
||||
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
||||
if (e) {
|
||||
for (const k in e) {
|
||||
if (k !== 'default') {
|
||||
const d = Object.getOwnPropertyDescriptor(e, k);
|
||||
Object.defineProperty(n, k, d.get ? d : {
|
||||
enumerable: true,
|
||||
get: () => e[k]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dropdown.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'dropdown';
|
||||
const DATA_KEY = 'bs.dropdown';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const ESCAPE_KEY = 'Escape';
|
||||
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 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_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_SHOW = 'show';
|
||||
const CLASS_NAME_DROPUP = 'dropup';
|
||||
const CLASS_NAME_DROPEND = 'dropend';
|
||||
const CLASS_NAME_DROPSTART = 'dropstart';
|
||||
const CLASS_NAME_DROPUP_CENTER = 'dropup-center';
|
||||
const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)';
|
||||
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;
|
||||
const SELECTOR_MENU = '.dropdown-menu';
|
||||
const SELECTOR_NAVBAR = '.navbar';
|
||||
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
|
||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
|
||||
const PLACEMENT_TOP = index_js.isRTL() ? 'top-end' : 'top-start';
|
||||
const PLACEMENT_TOPEND = index_js.isRTL() ? 'top-start' : 'top-end';
|
||||
const PLACEMENT_BOTTOM = index_js.isRTL() ? 'bottom-end' : 'bottom-start';
|
||||
const PLACEMENT_BOTTOMEND = index_js.isRTL() ? 'bottom-start' : 'bottom-end';
|
||||
const PLACEMENT_RIGHT = index_js.isRTL() ? 'left-start' : 'right-start';
|
||||
const PLACEMENT_LEFT = index_js.isRTL() ? 'right-start' : 'left-start';
|
||||
const PLACEMENT_TOPCENTER = 'top';
|
||||
const PLACEMENT_BOTTOMCENTER = 'bottom';
|
||||
const Default = {
|
||||
autoClose: true,
|
||||
boundary: 'clippingParents',
|
||||
display: 'dynamic',
|
||||
offset: [0, 2],
|
||||
popperConfig: null,
|
||||
reference: 'toggle'
|
||||
};
|
||||
const DefaultType = {
|
||||
autoClose: '(boolean|string)',
|
||||
boundary: '(string|element)',
|
||||
display: 'string',
|
||||
offset: '(array|string|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
reference: '(string|element|object)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Dropdown extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._popper = null;
|
||||
this._parent = this._element.parentNode; // dropdown wrapper
|
||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);
|
||||
this._inNavbar = this._detectNavbar();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
return this._isShown() ? this.hide() : this.show();
|
||||
}
|
||||
show() {
|
||||
if (index_js.isDisabled(this._element) || this._isShown()) {
|
||||
return;
|
||||
}
|
||||
const relatedTarget = {
|
||||
relatedTarget: this._element
|
||||
};
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget);
|
||||
if (showEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._createPopper();
|
||||
|
||||
// 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 && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.on(element, 'mouseover', index_js.noop);
|
||||
}
|
||||
}
|
||||
this._element.focus();
|
||||
this._element.setAttribute('aria-expanded', true);
|
||||
this._menu.classList.add(CLASS_NAME_SHOW);
|
||||
this._element.classList.add(CLASS_NAME_SHOW);
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);
|
||||
}
|
||||
hide() {
|
||||
if (index_js.isDisabled(this._element) || !this._isShown()) {
|
||||
return;
|
||||
}
|
||||
const relatedTarget = {
|
||||
relatedTarget: this._element
|
||||
};
|
||||
this._completeHide(relatedTarget);
|
||||
}
|
||||
dispose() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
update() {
|
||||
this._inNavbar = this._detectNavbar();
|
||||
if (this._popper) {
|
||||
this._popper.update();
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
_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) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.off(element, 'mouseover', index_js.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 = super._getConfig(config);
|
||||
if (typeof config.reference === 'object' && !index_js.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;
|
||||
}
|
||||
_createPopper() {
|
||||
if (typeof Popper__namespace === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)');
|
||||
}
|
||||
let referenceElement = this._element;
|
||||
if (this._config.reference === 'parent') {
|
||||
referenceElement = this._parent;
|
||||
} else if (index_js.isElement(this._config.reference)) {
|
||||
referenceElement = index_js.getElement(this._config.reference);
|
||||
} else if (typeof this._config.reference === 'object') {
|
||||
referenceElement = this._config.reference;
|
||||
}
|
||||
const popperConfig = this._getPopperConfig();
|
||||
this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
|
||||
}
|
||||
_isShown() {
|
||||
return this._menu.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
_getPlacement() {
|
||||
const parentDropdown = this._parent;
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
|
||||
return PLACEMENT_RIGHT;
|
||||
}
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
|
||||
return PLACEMENT_LEFT;
|
||||
}
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
|
||||
return PLACEMENT_TOPCENTER;
|
||||
}
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
|
||||
return PLACEMENT_BOTTOMCENTER;
|
||||
}
|
||||
|
||||
// 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(SELECTOR_NAVBAR) !== null;
|
||||
}
|
||||
_getOffset() {
|
||||
const {
|
||||
offset
|
||||
} = this._config;
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10));
|
||||
}
|
||||
if (typeof offset === 'function') {
|
||||
return popperData => offset(popperData, this._element);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
_getPopperConfig() {
|
||||
const defaultBsPopperConfig = {
|
||||
placement: this._getPlacement(),
|
||||
modifiers: [{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: this._config.boundary
|
||||
}
|
||||
}, {
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: this._getOffset()
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
// Disable Popper if we have a static display or Dropdown is in Navbar
|
||||
if (this._inNavbar || this._config.display === 'static') {
|
||||
Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove
|
||||
defaultBsPopperConfig.modifiers = [{
|
||||
name: 'applyStyles',
|
||||
enabled: false
|
||||
}];
|
||||
}
|
||||
return {
|
||||
...defaultBsPopperConfig,
|
||||
...index_js.execute(this._config.popperConfig, [defaultBsPopperConfig])
|
||||
};
|
||||
}
|
||||
_selectMenuItem({
|
||||
key,
|
||||
target
|
||||
}) {
|
||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));
|
||||
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
|
||||
index_js.getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Dropdown.getOrCreateInstance(this, config);
|
||||
if (typeof config !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config]();
|
||||
});
|
||||
}
|
||||
static clearMenus(event) {
|
||||
if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY) {
|
||||
return;
|
||||
}
|
||||
const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);
|
||||
for (const toggle of openToggles) {
|
||||
const context = Dropdown.getInstance(toggle);
|
||||
if (!context || context._config.autoClose === false) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
const relatedTarget = {
|
||||
relatedTarget: context._element
|
||||
};
|
||||
if (event.type === 'click') {
|
||||
relatedTarget.clickEvent = event;
|
||||
}
|
||||
context._completeHide(relatedTarget);
|
||||
}
|
||||
}
|
||||
static dataApiKeydownHandler(event) {
|
||||
// If not an UP | DOWN | ESCAPE key => not a dropdown command
|
||||
// If input/textarea && if key is other than ESCAPE => not a dropdown command
|
||||
|
||||
const isInput = /input|textarea/i.test(event.target.tagName);
|
||||
const isEscapeEvent = event.key === ESCAPE_KEY;
|
||||
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
|
||||
if (!isUpOrDownEvent && !isEscapeEvent) {
|
||||
return;
|
||||
}
|
||||
if (isInput && !isEscapeEvent) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);
|
||||
const instance = Dropdown.getOrCreateInstance(getToggleButton);
|
||||
if (isUpOrDownEvent) {
|
||||
event.stopPropagation();
|
||||
instance.show();
|
||||
instance._selectMenuItem(event);
|
||||
return;
|
||||
}
|
||||
if (instance._isShown()) {
|
||||
// else is escape and we check if it is shown
|
||||
event.stopPropagation();
|
||||
instance.hide();
|
||||
getToggleButton.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Dropdown);
|
||||
|
||||
return Dropdown;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=dropdown.js.map
|
1
bootstrap/js/dist/dropdown.js.map
vendored
Normal file
1
bootstrap/js/dist/dropdown.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
320
bootstrap/js/dist/modal.js
vendored
Normal file
320
bootstrap/js/dist/modal.js
vendored
Normal file
|
@ -0,0 +1,320 @@
|
|||
/*!
|
||||
* Bootstrap modal.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Modal = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap modal.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'modal';
|
||||
const DATA_KEY = 'bs.modal';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const ESCAPE_KEY = 'Escape';
|
||||
const EVENT_HIDE = `hide${EVENT_KEY}`;
|
||||
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_RESIZE = `resize${EVENT_KEY}`;
|
||||
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;
|
||||
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`;
|
||||
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;
|
||||
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
|
||||
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-bs-toggle="modal"]';
|
||||
const Default = {
|
||||
backdrop: true,
|
||||
focus: true,
|
||||
keyboard: true
|
||||
};
|
||||
const DefaultType = {
|
||||
backdrop: '(boolean|string)',
|
||||
focus: 'boolean',
|
||||
keyboard: 'boolean'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Modal extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);
|
||||
this._backdrop = this._initializeBackDrop();
|
||||
this._focustrap = this._initializeFocusTrap();
|
||||
this._isShown = false;
|
||||
this._isTransitioning = false;
|
||||
this._scrollBar = new ScrollBarHelper();
|
||||
this._addEventListeners();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle(relatedTarget) {
|
||||
return this._isShown ? this.hide() : this.show(relatedTarget);
|
||||
}
|
||||
show(relatedTarget) {
|
||||
if (this._isShown || this._isTransitioning) {
|
||||
return;
|
||||
}
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
|
||||
relatedTarget
|
||||
});
|
||||
if (showEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._isShown = true;
|
||||
this._isTransitioning = true;
|
||||
this._scrollBar.hide();
|
||||
document.body.classList.add(CLASS_NAME_OPEN);
|
||||
this._adjustDialog();
|
||||
this._backdrop.show(() => this._showElement(relatedTarget));
|
||||
}
|
||||
hide() {
|
||||
if (!this._isShown || this._isTransitioning) {
|
||||
return;
|
||||
}
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._isShown = false;
|
||||
this._isTransitioning = true;
|
||||
this._focustrap.deactivate();
|
||||
this._element.classList.remove(CLASS_NAME_SHOW);
|
||||
this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());
|
||||
}
|
||||
dispose() {
|
||||
EventHandler.off(window, EVENT_KEY);
|
||||
EventHandler.off(this._dialog, EVENT_KEY);
|
||||
this._backdrop.dispose();
|
||||
this._focustrap.deactivate();
|
||||
super.dispose();
|
||||
}
|
||||
handleUpdate() {
|
||||
this._adjustDialog();
|
||||
}
|
||||
|
||||
// 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
|
||||
});
|
||||
}
|
||||
_showElement(relatedTarget) {
|
||||
// try to append dynamic modal
|
||||
if (!document.body.contains(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;
|
||||
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);
|
||||
if (modalBody) {
|
||||
modalBody.scrollTop = 0;
|
||||
}
|
||||
index_js.reflow(this._element);
|
||||
this._element.classList.add(CLASS_NAME_SHOW);
|
||||
const transitionComplete = () => {
|
||||
if (this._config.focus) {
|
||||
this._focustrap.activate();
|
||||
}
|
||||
this._isTransitioning = false;
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, {
|
||||
relatedTarget
|
||||
});
|
||||
};
|
||||
this._queueCallback(transitionComplete, this._dialog, this._isAnimated());
|
||||
}
|
||||
_addEventListeners() {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
|
||||
if (event.key !== ESCAPE_KEY) {
|
||||
return;
|
||||
}
|
||||
if (this._config.keyboard) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this._triggerBackdropTransition();
|
||||
});
|
||||
EventHandler.on(window, EVENT_RESIZE, () => {
|
||||
if (this._isShown && !this._isTransitioning) {
|
||||
this._adjustDialog();
|
||||
}
|
||||
});
|
||||
EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {
|
||||
// a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks
|
||||
EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {
|
||||
if (this._element !== event.target || this._element !== event2.target) {
|
||||
return;
|
||||
}
|
||||
if (this._config.backdrop === 'static') {
|
||||
this._triggerBackdropTransition();
|
||||
return;
|
||||
}
|
||||
if (this._config.backdrop) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
_hideModal() {
|
||||
this._element.style.display = 'none';
|
||||
this._element.setAttribute('aria-hidden', true);
|
||||
this._element.removeAttribute('aria-modal');
|
||||
this._element.removeAttribute('role');
|
||||
this._isTransitioning = false;
|
||||
this._backdrop.hide(() => {
|
||||
document.body.classList.remove(CLASS_NAME_OPEN);
|
||||
this._resetAdjustments();
|
||||
this._scrollBar.reset();
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN);
|
||||
});
|
||||
}
|
||||
_isAnimated() {
|
||||
return this._element.classList.contains(CLASS_NAME_FADE);
|
||||
}
|
||||
_triggerBackdropTransition() {
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
|
||||
const initialOverflowY = this._element.style.overflowY;
|
||||
// return if the following background transition hasn't yet completed
|
||||
if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {
|
||||
return;
|
||||
}
|
||||
if (!isModalOverflowing) {
|
||||
this._element.style.overflowY = 'hidden';
|
||||
}
|
||||
this._element.classList.add(CLASS_NAME_STATIC);
|
||||
this._queueCallback(() => {
|
||||
this._element.classList.remove(CLASS_NAME_STATIC);
|
||||
this._queueCallback(() => {
|
||||
this._element.style.overflowY = initialOverflowY;
|
||||
}, this._dialog);
|
||||
}, this._dialog);
|
||||
this._element.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* The following methods are used to handle overflowing modals
|
||||
*/
|
||||
|
||||
_adjustDialog() {
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
|
||||
const scrollbarWidth = this._scrollBar.getWidth();
|
||||
const isBodyOverflowing = scrollbarWidth > 0;
|
||||
if (isBodyOverflowing && !isModalOverflowing) {
|
||||
const property = index_js.isRTL() ? 'paddingLeft' : 'paddingRight';
|
||||
this._element.style[property] = `${scrollbarWidth}px`;
|
||||
}
|
||||
if (!isBodyOverflowing && isModalOverflowing) {
|
||||
const property = index_js.isRTL() ? 'paddingRight' : 'paddingLeft';
|
||||
this._element.style[property] = `${scrollbarWidth}px`;
|
||||
}
|
||||
}
|
||||
_resetAdjustments() {
|
||||
this._element.style.paddingLeft = '';
|
||||
this._element.style.paddingRight = '';
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config, relatedTarget) {
|
||||
return this.each(function () {
|
||||
const data = Modal.getOrCreateInstance(this, config);
|
||||
if (typeof config !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config](relatedTarget);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
const target = SelectorEngine.getElementFromSelector(this);
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
EventHandler.one(target, EVENT_SHOW, showEvent => {
|
||||
if (showEvent.defaultPrevented) {
|
||||
// only register focus restorer if modal will actually get shown
|
||||
return;
|
||||
}
|
||||
EventHandler.one(target, EVENT_HIDDEN, () => {
|
||||
if (index_js.isVisible(this)) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// avoid conflict when clicking modal toggler while another one is open
|
||||
const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);
|
||||
if (alreadyOpen) {
|
||||
Modal.getInstance(alreadyOpen).hide();
|
||||
}
|
||||
const data = Modal.getOrCreateInstance(target);
|
||||
data.toggle(this);
|
||||
});
|
||||
componentFunctions_js.enableDismissTrigger(Modal);
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Modal);
|
||||
|
||||
return Modal;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=modal.js.map
|
1
bootstrap/js/dist/modal.js.map
vendored
Normal file
1
bootstrap/js/dist/modal.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
246
bootstrap/js/dist/offcanvas.js
vendored
Normal file
246
bootstrap/js/dist/offcanvas.js
vendored
Normal file
|
@ -0,0 +1,246 @@
|
|||
/*!
|
||||
* Bootstrap offcanvas.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Offcanvas = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap offcanvas.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 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 CLASS_NAME_SHOW = 'show';
|
||||
const CLASS_NAME_SHOWING = 'showing';
|
||||
const CLASS_NAME_HIDING = 'hiding';
|
||||
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_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
|
||||
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
|
||||
const EVENT_RESIZE = `resize${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"]';
|
||||
const Default = {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
scroll: false
|
||||
};
|
||||
const DefaultType = {
|
||||
backdrop: '(boolean|string)',
|
||||
keyboard: 'boolean',
|
||||
scroll: 'boolean'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Offcanvas extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._isShown = false;
|
||||
this._backdrop = this._initializeBackDrop();
|
||||
this._focustrap = this._initializeFocusTrap();
|
||||
this._addEventListeners();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// 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._backdrop.show();
|
||||
if (!this._config.scroll) {
|
||||
new ScrollBarHelper().hide();
|
||||
}
|
||||
this._element.setAttribute('aria-modal', true);
|
||||
this._element.setAttribute('role', 'dialog');
|
||||
this._element.classList.add(CLASS_NAME_SHOWING);
|
||||
const completeCallBack = () => {
|
||||
if (!this._config.scroll || this._config.backdrop) {
|
||||
this._focustrap.activate();
|
||||
}
|
||||
this._element.classList.add(CLASS_NAME_SHOW);
|
||||
this._element.classList.remove(CLASS_NAME_SHOWING);
|
||||
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.add(CLASS_NAME_HIDING);
|
||||
this._backdrop.hide();
|
||||
const completeCallback = () => {
|
||||
this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING);
|
||||
this._element.removeAttribute('aria-modal');
|
||||
this._element.removeAttribute('role');
|
||||
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
|
||||
_initializeBackDrop() {
|
||||
const clickCallback = () => {
|
||||
if (this._config.backdrop === 'static') {
|
||||
EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
};
|
||||
|
||||
// 'static' option will be translated to true, and booleans will keep their value
|
||||
const isVisible = Boolean(this._config.backdrop);
|
||||
return new Backdrop({
|
||||
className: CLASS_NAME_BACKDROP,
|
||||
isVisible,
|
||||
isAnimated: true,
|
||||
rootElement: this._element.parentNode,
|
||||
clickCallback: isVisible ? clickCallback : null
|
||||
});
|
||||
}
|
||||
_initializeFocusTrap() {
|
||||
return new FocusTrap({
|
||||
trapElement: this._element
|
||||
});
|
||||
}
|
||||
_addEventListeners() {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
|
||||
if (event.key !== ESCAPE_KEY) {
|
||||
return;
|
||||
}
|
||||
if (this._config.keyboard) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
|
||||
});
|
||||
}
|
||||
|
||||
// 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 = SelectorEngine.getElementFromSelector(this);
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (index_js.isDisabled(this)) {
|
||||
return;
|
||||
}
|
||||
EventHandler.one(target, EVENT_HIDDEN, () => {
|
||||
// focus on trigger when it is closed
|
||||
if (index_js.isVisible(this)) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// avoid conflict when clicking a toggler of an offcanvas, while another is open
|
||||
const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);
|
||||
if (alreadyOpen && alreadyOpen !== target) {
|
||||
Offcanvas.getInstance(alreadyOpen).hide();
|
||||
}
|
||||
const data = Offcanvas.getOrCreateInstance(target);
|
||||
data.toggle(this);
|
||||
});
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {
|
||||
Offcanvas.getOrCreateInstance(selector).show();
|
||||
}
|
||||
});
|
||||
EventHandler.on(window, EVENT_RESIZE, () => {
|
||||
for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {
|
||||
if (getComputedStyle(element).position !== 'fixed') {
|
||||
Offcanvas.getOrCreateInstance(element).hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
componentFunctions_js.enableDismissTrigger(Offcanvas);
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Offcanvas);
|
||||
|
||||
return Offcanvas;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=offcanvas.js.map
|
1
bootstrap/js/dist/offcanvas.js.map
vendored
Normal file
1
bootstrap/js/dist/offcanvas.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
96
bootstrap/js/dist/popover.js
vendored
Normal file
96
bootstrap/js/dist/popover.js
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*!
|
||||
* Bootstrap popover.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./tooltip', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip, global.Index));
|
||||
})(this, (function (Tooltip, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap popover.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'popover';
|
||||
const SELECTOR_TITLE = '.popover-header';
|
||||
const SELECTOR_CONTENT = '.popover-body';
|
||||
const Default = {
|
||||
...Tooltip.Default,
|
||||
content: '',
|
||||
offset: [0, 8],
|
||||
placement: 'right',
|
||||
template: '<div class="popover" role="tooltip">' + '<div class="popover-arrow"></div>' + '<h3 class="popover-header"></h3>' + '<div class="popover-body"></div>' + '</div>',
|
||||
trigger: 'click'
|
||||
};
|
||||
const DefaultType = {
|
||||
...Tooltip.DefaultType,
|
||||
content: '(null|string|element|function)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Popover extends Tooltip {
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Overrides
|
||||
_isWithContent() {
|
||||
return this._getTitle() || this._getContent();
|
||||
}
|
||||
|
||||
// Private
|
||||
_getContentForTemplate() {
|
||||
return {
|
||||
[SELECTOR_TITLE]: this._getTitle(),
|
||||
[SELECTOR_CONTENT]: this._getContent()
|
||||
};
|
||||
}
|
||||
_getContent() {
|
||||
return this._resolvePossibleFunction(this._config.content);
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Popover.getOrCreateInstance(this, config);
|
||||
if (typeof config !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config]();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Popover);
|
||||
|
||||
return Popover;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=popover.js.map
|
1
bootstrap/js/dist/popover.js.map
vendored
Normal file
1
bootstrap/js/dist/popover.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"popover.js","sources":["../src/popover.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Tooltip from './tooltip.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'popover'\n\nconst SELECTOR_TITLE = '.popover-header'\nconst SELECTOR_CONTENT = '.popover-body'\n\nconst Default = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '<div class=\"popover\" role=\"tooltip\">' +\n '<div class=\"popover-arrow\"></div>' +\n '<h3 class=\"popover-header\"></h3>' +\n '<div class=\"popover-body\"></div>' +\n '</div>',\n trigger: 'click'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n}\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent()\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n }\n }\n\n _getContent() {\n return this._resolvePossibleFunction(this._config.content)\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n })\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover)\n\nexport default Popover\n"],"names":["NAME","SELECTOR_TITLE","SELECTOR_CONTENT","Default","Tooltip","content","offset","placement","template","trigger","DefaultType","Popover","_isWithContent","_getTitle","_getContent","_getContentForTemplate","_resolvePossibleFunction","_config","jQueryInterface","config","each","data","getOrCreateInstance","TypeError","defineJQueryPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAKA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,SAAS,CAAA;EAEtB,MAAMC,cAAc,GAAG,iBAAiB,CAAA;EACxC,MAAMC,gBAAgB,GAAG,eAAe,CAAA;EAExC,MAAMC,OAAO,GAAG;IACd,GAAGC,OAAO,CAACD,OAAO;EAClBE,EAAAA,OAAO,EAAE,EAAE;EACXC,EAAAA,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;EACdC,EAAAA,SAAS,EAAE,OAAO;IAClBC,QAAQ,EAAE,sCAAsC,GAC9C,mCAAmC,GACnC,kCAAkC,GAClC,kCAAkC,GAClC,QAAQ;EACVC,EAAAA,OAAO,EAAE,OAAA;EACX,CAAC,CAAA;EAED,MAAMC,WAAW,GAAG;IAClB,GAAGN,OAAO,CAACM,WAAW;EACtBL,EAAAA,OAAO,EAAE,gCAAA;EACX,CAAC,CAAA;;EAED;EACA;EACA;;EAEA,MAAMM,OAAO,SAASP,OAAO,CAAC;EAC5B;IACA,WAAWD,OAAOA,GAAG;EACnB,IAAA,OAAOA,OAAO,CAAA;EAChB,GAAA;IAEA,WAAWO,WAAWA,GAAG;EACvB,IAAA,OAAOA,WAAW,CAAA;EACpB,GAAA;IAEA,WAAWV,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI,CAAA;EACb,GAAA;;EAEA;EACAY,EAAAA,cAAcA,GAAG;MACf,OAAO,IAAI,CAACC,SAAS,EAAE,IAAI,IAAI,CAACC,WAAW,EAAE,CAAA;EAC/C,GAAA;;EAEA;EACAC,EAAAA,sBAAsBA,GAAG;MACvB,OAAO;EACL,MAAA,CAACd,cAAc,GAAG,IAAI,CAACY,SAAS,EAAE;EAClC,MAAA,CAACX,gBAAgB,GAAG,IAAI,CAACY,WAAW,EAAC;OACtC,CAAA;EACH,GAAA;EAEAA,EAAAA,WAAWA,GAAG;MACZ,OAAO,IAAI,CAACE,wBAAwB,CAAC,IAAI,CAACC,OAAO,CAACZ,OAAO,CAAC,CAAA;EAC5D,GAAA;;EAEA;IACA,OAAOa,eAAeA,CAACC,MAAM,EAAE;EAC7B,IAAA,OAAO,IAAI,CAACC,IAAI,CAAC,YAAY;QAC3B,MAAMC,IAAI,GAAGV,OAAO,CAACW,mBAAmB,CAAC,IAAI,EAAEH,MAAM,CAAC,CAAA;EAEtD,MAAA,IAAI,OAAOA,MAAM,KAAK,QAAQ,EAAE;EAC9B,QAAA,OAAA;EACF,OAAA;EAEA,MAAA,IAAI,OAAOE,IAAI,CAACF,MAAM,CAAC,KAAK,WAAW,EAAE;EACvC,QAAA,MAAM,IAAII,SAAS,CAAE,CAAmBJ,iBAAAA,EAAAA,MAAO,GAAE,CAAC,CAAA;EACpD,OAAA;EAEAE,MAAAA,IAAI,CAACF,MAAM,CAAC,EAAE,CAAA;EAChB,KAAC,CAAC,CAAA;EACJ,GAAA;EACF,CAAA;;EAEA;EACA;EACA;;AAEAK,6BAAkB,CAACb,OAAO,CAAC;;;;;;;;"}
|
275
bootstrap/js/dist/scrollspy.js
vendored
Normal file
275
bootstrap/js/dist/scrollspy.js
vendored
Normal file
|
@ -0,0 +1,275 @@
|
|||
/*!
|
||||
* Bootstrap scrollspy.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollspy = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap scrollspy.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'scrollspy';
|
||||
const DATA_KEY = 'bs.scrollspy';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const EVENT_ACTIVATE = `activate${EVENT_KEY}`;
|
||||
const EVENT_CLICK = `click${EVENT_KEY}`;
|
||||
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-bs-spy="scroll"]';
|
||||
const SELECTOR_TARGET_LINKS = '[href]';
|
||||
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_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;
|
||||
const SELECTOR_DROPDOWN = '.dropdown';
|
||||
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
|
||||
const Default = {
|
||||
offset: null,
|
||||
// TODO: v6 @deprecated, keep it for backwards compatibility reasons
|
||||
rootMargin: '0px 0px -25%',
|
||||
smoothScroll: false,
|
||||
target: null,
|
||||
threshold: [0.1, 0.5, 1]
|
||||
};
|
||||
const DefaultType = {
|
||||
offset: '(number|null)',
|
||||
// TODO v6 @deprecated, keep it for backwards compatibility reasons
|
||||
rootMargin: 'string',
|
||||
smoothScroll: 'boolean',
|
||||
target: 'element',
|
||||
threshold: 'array'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class ScrollSpy extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
|
||||
// this._element is the observablesContainer and config.target the menu links wrapper
|
||||
this._targetLinks = new Map();
|
||||
this._observableSections = new Map();
|
||||
this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;
|
||||
this._activeTarget = null;
|
||||
this._observer = null;
|
||||
this._previousScrollData = {
|
||||
visibleEntryTop: 0,
|
||||
parentScrollTop: 0
|
||||
};
|
||||
this.refresh(); // initialize
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
refresh() {
|
||||
this._initializeTargetsAndObservables();
|
||||
this._maybeEnableSmoothScroll();
|
||||
if (this._observer) {
|
||||
this._observer.disconnect();
|
||||
} else {
|
||||
this._observer = this._getNewObserver();
|
||||
}
|
||||
for (const section of this._observableSections.values()) {
|
||||
this._observer.observe(section);
|
||||
}
|
||||
}
|
||||
dispose() {
|
||||
this._observer.disconnect();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config) {
|
||||
// TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case
|
||||
config.target = index_js.getElement(config.target) || document.body;
|
||||
|
||||
// TODO: v6 Only for backwards compatibility reasons. Use rootMargin only
|
||||
config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;
|
||||
if (typeof config.threshold === 'string') {
|
||||
config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
_maybeEnableSmoothScroll() {
|
||||
if (!this._config.smoothScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
// unregister any previous listeners
|
||||
EventHandler.off(this._config.target, EVENT_CLICK);
|
||||
EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {
|
||||
const observableSection = this._observableSections.get(event.target.hash);
|
||||
if (observableSection) {
|
||||
event.preventDefault();
|
||||
const root = this._rootElement || window;
|
||||
const height = observableSection.offsetTop - this._element.offsetTop;
|
||||
if (root.scrollTo) {
|
||||
root.scrollTo({
|
||||
top: height,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Chrome 60 doesn't support `scrollTo`
|
||||
root.scrollTop = height;
|
||||
}
|
||||
});
|
||||
}
|
||||
_getNewObserver() {
|
||||
const options = {
|
||||
root: this._rootElement,
|
||||
threshold: this._config.threshold,
|
||||
rootMargin: this._config.rootMargin
|
||||
};
|
||||
return new IntersectionObserver(entries => this._observerCallback(entries), options);
|
||||
}
|
||||
|
||||
// The logic of selection
|
||||
_observerCallback(entries) {
|
||||
const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);
|
||||
const activate = entry => {
|
||||
this._previousScrollData.visibleEntryTop = entry.target.offsetTop;
|
||||
this._process(targetElement(entry));
|
||||
};
|
||||
const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;
|
||||
const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;
|
||||
this._previousScrollData.parentScrollTop = parentScrollTop;
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) {
|
||||
this._activeTarget = null;
|
||||
this._clearActiveClass(targetElement(entry));
|
||||
continue;
|
||||
}
|
||||
const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;
|
||||
// if we are scrolling down, pick the bigger offsetTop
|
||||
if (userScrollsDown && entryIsLowerThanPrevious) {
|
||||
activate(entry);
|
||||
// if parent isn't scrolled, let's keep the first visible item, breaking the iteration
|
||||
if (!parentScrollTop) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we are scrolling up, pick the smallest offsetTop
|
||||
if (!userScrollsDown && !entryIsLowerThanPrevious) {
|
||||
activate(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
_initializeTargetsAndObservables() {
|
||||
this._targetLinks = new Map();
|
||||
this._observableSections = new Map();
|
||||
const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);
|
||||
for (const anchor of targetLinks) {
|
||||
// ensure that the anchor has an id and is not disabled
|
||||
if (!anchor.hash || index_js.isDisabled(anchor)) {
|
||||
continue;
|
||||
}
|
||||
const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);
|
||||
|
||||
// ensure that the observableSection exists & is visible
|
||||
if (index_js.isVisible(observableSection)) {
|
||||
this._targetLinks.set(decodeURI(anchor.hash), anchor);
|
||||
this._observableSections.set(anchor.hash, observableSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
_process(target) {
|
||||
if (this._activeTarget === target) {
|
||||
return;
|
||||
}
|
||||
this._clearActiveClass(this._config.target);
|
||||
this._activeTarget = target;
|
||||
target.classList.add(CLASS_NAME_ACTIVE);
|
||||
this._activateParents(target);
|
||||
EventHandler.trigger(this._element, EVENT_ACTIVATE, {
|
||||
relatedTarget: target
|
||||
});
|
||||
}
|
||||
_activateParents(target) {
|
||||
// Activate dropdown parents
|
||||
if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {
|
||||
SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE);
|
||||
return;
|
||||
}
|
||||
for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {
|
||||
// Set triggered links parents as active
|
||||
// With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
|
||||
for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {
|
||||
item.classList.add(CLASS_NAME_ACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
_clearActiveClass(parent) {
|
||||
parent.classList.remove(CLASS_NAME_ACTIVE);
|
||||
const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent);
|
||||
for (const node of activeNodes) {
|
||||
node.classList.remove(CLASS_NAME_ACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = ScrollSpy.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]();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {
|
||||
ScrollSpy.getOrCreateInstance(spy);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(ScrollSpy);
|
||||
|
||||
return ScrollSpy;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=scrollspy.js.map
|
1
bootstrap/js/dist/scrollspy.js.map
vendored
Normal file
1
bootstrap/js/dist/scrollspy.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
285
bootstrap/js/dist/tab.js
vendored
Normal file
285
bootstrap/js/dist/tab.js
vendored
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*!
|
||||
* Bootstrap tab.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tab = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap tab.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'tab';
|
||||
const DATA_KEY = 'bs.tab';
|
||||
const EVENT_KEY = `.${DATA_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_DATA_API = `click${EVENT_KEY}`;
|
||||
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;
|
||||
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`;
|
||||
const ARROW_LEFT_KEY = 'ArrowLeft';
|
||||
const ARROW_RIGHT_KEY = 'ArrowRight';
|
||||
const ARROW_UP_KEY = 'ArrowUp';
|
||||
const ARROW_DOWN_KEY = 'ArrowDown';
|
||||
const HOME_KEY = 'Home';
|
||||
const END_KEY = 'End';
|
||||
const CLASS_NAME_ACTIVE = 'active';
|
||||
const CLASS_NAME_FADE = 'fade';
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const CLASS_DROPDOWN = 'dropdown';
|
||||
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
|
||||
const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';
|
||||
const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`;
|
||||
const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]';
|
||||
const SELECTOR_OUTER = '.nav-item, .list-group-item';
|
||||
const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]'; // TODO: could only be `tab` in v6
|
||||
const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;
|
||||
const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]`;
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Tab extends BaseComponent {
|
||||
constructor(element) {
|
||||
super(element);
|
||||
this._parent = this._element.closest(SELECTOR_TAB_PANEL);
|
||||
if (!this._parent) {
|
||||
return;
|
||||
// TODO: should throw exception in v6
|
||||
// throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)
|
||||
}
|
||||
|
||||
// Set up initial aria attributes
|
||||
this._setInitialAttributes(this._parent, this._getChildren());
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
show() {
|
||||
// Shows this elem and deactivate the active sibling if exists
|
||||
const innerElem = this._element;
|
||||
if (this._elemIsActive(innerElem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for active tab on same parent to deactivate it
|
||||
const active = this._getActiveElem();
|
||||
const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE, {
|
||||
relatedTarget: innerElem
|
||||
}) : null;
|
||||
const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, {
|
||||
relatedTarget: active
|
||||
});
|
||||
if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._deactivate(active, innerElem);
|
||||
this._activate(innerElem, active);
|
||||
}
|
||||
|
||||
// Private
|
||||
_activate(element, relatedElem) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.classList.add(CLASS_NAME_ACTIVE);
|
||||
this._activate(SelectorEngine.getElementFromSelector(element)); // Search and activate/show the proper section
|
||||
|
||||
const complete = () => {
|
||||
if (element.getAttribute('role') !== 'tab') {
|
||||
element.classList.add(CLASS_NAME_SHOW);
|
||||
return;
|
||||
}
|
||||
element.removeAttribute('tabindex');
|
||||
element.setAttribute('aria-selected', true);
|
||||
this._toggleDropDown(element, true);
|
||||
EventHandler.trigger(element, EVENT_SHOWN, {
|
||||
relatedTarget: relatedElem
|
||||
});
|
||||
};
|
||||
this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));
|
||||
}
|
||||
_deactivate(element, relatedElem) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.classList.remove(CLASS_NAME_ACTIVE);
|
||||
element.blur();
|
||||
this._deactivate(SelectorEngine.getElementFromSelector(element)); // Search and deactivate the shown section too
|
||||
|
||||
const complete = () => {
|
||||
if (element.getAttribute('role') !== 'tab') {
|
||||
element.classList.remove(CLASS_NAME_SHOW);
|
||||
return;
|
||||
}
|
||||
element.setAttribute('aria-selected', false);
|
||||
element.setAttribute('tabindex', '-1');
|
||||
this._toggleDropDown(element, false);
|
||||
EventHandler.trigger(element, EVENT_HIDDEN, {
|
||||
relatedTarget: relatedElem
|
||||
});
|
||||
};
|
||||
this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));
|
||||
}
|
||||
_keydown(event) {
|
||||
if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key)) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page
|
||||
event.preventDefault();
|
||||
const children = this._getChildren().filter(element => !index_js.isDisabled(element));
|
||||
let nextActiveElement;
|
||||
if ([HOME_KEY, END_KEY].includes(event.key)) {
|
||||
nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1];
|
||||
} else {
|
||||
const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);
|
||||
nextActiveElement = index_js.getNextActiveElement(children, event.target, isNext, true);
|
||||
}
|
||||
if (nextActiveElement) {
|
||||
nextActiveElement.focus({
|
||||
preventScroll: true
|
||||
});
|
||||
Tab.getOrCreateInstance(nextActiveElement).show();
|
||||
}
|
||||
}
|
||||
_getChildren() {
|
||||
// collection of inner elements
|
||||
return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent);
|
||||
}
|
||||
_getActiveElem() {
|
||||
return this._getChildren().find(child => this._elemIsActive(child)) || null;
|
||||
}
|
||||
_setInitialAttributes(parent, children) {
|
||||
this._setAttributeIfNotExists(parent, 'role', 'tablist');
|
||||
for (const child of children) {
|
||||
this._setInitialAttributesOnChild(child);
|
||||
}
|
||||
}
|
||||
_setInitialAttributesOnChild(child) {
|
||||
child = this._getInnerElement(child);
|
||||
const isActive = this._elemIsActive(child);
|
||||
const outerElem = this._getOuterElement(child);
|
||||
child.setAttribute('aria-selected', isActive);
|
||||
if (outerElem !== child) {
|
||||
this._setAttributeIfNotExists(outerElem, 'role', 'presentation');
|
||||
}
|
||||
if (!isActive) {
|
||||
child.setAttribute('tabindex', '-1');
|
||||
}
|
||||
this._setAttributeIfNotExists(child, 'role', 'tab');
|
||||
|
||||
// set attributes to the related panel too
|
||||
this._setInitialAttributesOnTargetPanel(child);
|
||||
}
|
||||
_setInitialAttributesOnTargetPanel(child) {
|
||||
const target = SelectorEngine.getElementFromSelector(child);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
this._setAttributeIfNotExists(target, 'role', 'tabpanel');
|
||||
if (child.id) {
|
||||
this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`);
|
||||
}
|
||||
}
|
||||
_toggleDropDown(element, open) {
|
||||
const outerElem = this._getOuterElement(element);
|
||||
if (!outerElem.classList.contains(CLASS_DROPDOWN)) {
|
||||
return;
|
||||
}
|
||||
const toggle = (selector, className) => {
|
||||
const element = SelectorEngine.findOne(selector, outerElem);
|
||||
if (element) {
|
||||
element.classList.toggle(className, open);
|
||||
}
|
||||
};
|
||||
toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);
|
||||
toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW);
|
||||
outerElem.setAttribute('aria-expanded', open);
|
||||
}
|
||||
_setAttributeIfNotExists(element, attribute, value) {
|
||||
if (!element.hasAttribute(attribute)) {
|
||||
element.setAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
_elemIsActive(elem) {
|
||||
return elem.classList.contains(CLASS_NAME_ACTIVE);
|
||||
}
|
||||
|
||||
// Try to get the inner element (usually the .nav-link)
|
||||
_getInnerElement(elem) {
|
||||
return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem);
|
||||
}
|
||||
|
||||
// Try to get the outer element (usually the .nav-item)
|
||||
_getOuterElement(elem) {
|
||||
return elem.closest(SELECTOR_OUTER) || elem;
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Tab.getOrCreateInstance(this);
|
||||
if (typeof config !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config]();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (index_js.isDisabled(this)) {
|
||||
return;
|
||||
}
|
||||
Tab.getOrCreateInstance(this).show();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize on focus
|
||||
*/
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {
|
||||
Tab.getOrCreateInstance(element);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Tab);
|
||||
|
||||
return Tab;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=tab.js.map
|
1
bootstrap/js/dist/tab.js.map
vendored
Normal file
1
bootstrap/js/dist/tab.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
199
bootstrap/js/dist/toast.js
vendored
Normal file
199
bootstrap/js/dist/toast.js
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*!
|
||||
* Bootstrap toast.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Toast = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions, global.Index));
|
||||
})(this, (function (BaseComponent, EventHandler, componentFunctions_js, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap toast.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'toast';
|
||||
const DATA_KEY = 'bs.toast';
|
||||
const EVENT_KEY = `.${DATA_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'; // @deprecated - kept here only for backwards compatibility
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const CLASS_NAME_SHOWING = 'showing';
|
||||
const DefaultType = {
|
||||
animation: 'boolean',
|
||||
autohide: 'boolean',
|
||||
delay: 'number'
|
||||
};
|
||||
const Default = {
|
||||
animation: true,
|
||||
autohide: true,
|
||||
delay: 5000
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Toast extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._timeout = null;
|
||||
this._hasMouseInteraction = false;
|
||||
this._hasKeyboardInteraction = false;
|
||||
this._setListeners();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
show() {
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
|
||||
if (showEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._clearTimeout();
|
||||
if (this._config.animation) {
|
||||
this._element.classList.add(CLASS_NAME_FADE);
|
||||
}
|
||||
const complete = () => {
|
||||
this._element.classList.remove(CLASS_NAME_SHOWING);
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN);
|
||||
this._maybeScheduleHide();
|
||||
};
|
||||
this._element.classList.remove(CLASS_NAME_HIDE); // @deprecated
|
||||
index_js.reflow(this._element);
|
||||
this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING);
|
||||
this._queueCallback(complete, this._element, this._config.animation);
|
||||
}
|
||||
hide() {
|
||||
if (!this.isShown()) {
|
||||
return;
|
||||
}
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
const complete = () => {
|
||||
this._element.classList.add(CLASS_NAME_HIDE); // @deprecated
|
||||
this._element.classList.remove(CLASS_NAME_SHOWING, 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() {
|
||||
this._clearTimeout();
|
||||
if (this.isShown()) {
|
||||
this._element.classList.remove(CLASS_NAME_SHOW);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
isShown() {
|
||||
return this._element.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_maybeScheduleHide() {
|
||||
if (!this._config.autohide) {
|
||||
return;
|
||||
}
|
||||
if (this._hasMouseInteraction || this._hasKeyboardInteraction) {
|
||||
return;
|
||||
}
|
||||
this._timeout = setTimeout(() => {
|
||||
this.hide();
|
||||
}, this._config.delay);
|
||||
}
|
||||
_onInteraction(event, isInteracting) {
|
||||
switch (event.type) {
|
||||
case 'mouseover':
|
||||
case 'mouseout':
|
||||
{
|
||||
this._hasMouseInteraction = isInteracting;
|
||||
break;
|
||||
}
|
||||
case 'focusin':
|
||||
case 'focusout':
|
||||
{
|
||||
this._hasKeyboardInteraction = isInteracting;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isInteracting) {
|
||||
this._clearTimeout();
|
||||
return;
|
||||
}
|
||||
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() {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Toast.getOrCreateInstance(this, config);
|
||||
if (typeof config === 'string') {
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config](this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
componentFunctions_js.enableDismissTrigger(Toast);
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Toast);
|
||||
|
||||
return Toast;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=toast.js.map
|
1
bootstrap/js/dist/toast.js.map
vendored
Normal file
1
bootstrap/js/dist/toast.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
546
bootstrap/js/dist/tooltip.js
vendored
Normal file
546
bootstrap/js/dist/tooltip.js
vendored
Normal file
|
@ -0,0 +1,546 @@
|
|||
/*!
|
||||
* Bootstrap tooltip.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./util/index.js'), require('./util/sanitizer.js'), require('./util/template-factory.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './util/index', './util/sanitizer', './util/template-factory'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.Index, global.Sanitizer, global.TemplateFactory));
|
||||
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, index_js, sanitizer_js, TemplateFactory) { 'use strict';
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
||||
if (e) {
|
||||
for (const k in e) {
|
||||
if (k !== 'default') {
|
||||
const d = Object.getOwnPropertyDescriptor(e, k);
|
||||
Object.defineProperty(n, k, d.get ? d : {
|
||||
enumerable: true,
|
||||
get: () => e[k]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap tooltip.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'tooltip';
|
||||
const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);
|
||||
const CLASS_NAME_FADE = 'fade';
|
||||
const CLASS_NAME_MODAL = 'modal';
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';
|
||||
const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;
|
||||
const EVENT_MODAL_HIDE = 'hide.bs.modal';
|
||||
const TRIGGER_HOVER = 'hover';
|
||||
const TRIGGER_FOCUS = 'focus';
|
||||
const TRIGGER_CLICK = 'click';
|
||||
const TRIGGER_MANUAL = 'manual';
|
||||
const EVENT_HIDE = 'hide';
|
||||
const EVENT_HIDDEN = 'hidden';
|
||||
const EVENT_SHOW = 'show';
|
||||
const EVENT_SHOWN = 'shown';
|
||||
const EVENT_INSERTED = 'inserted';
|
||||
const EVENT_CLICK = 'click';
|
||||
const EVENT_FOCUSIN = 'focusin';
|
||||
const EVENT_FOCUSOUT = 'focusout';
|
||||
const EVENT_MOUSEENTER = 'mouseenter';
|
||||
const EVENT_MOUSELEAVE = 'mouseleave';
|
||||
const AttachmentMap = {
|
||||
AUTO: 'auto',
|
||||
TOP: 'top',
|
||||
RIGHT: index_js.isRTL() ? 'left' : 'right',
|
||||
BOTTOM: 'bottom',
|
||||
LEFT: index_js.isRTL() ? 'right' : 'left'
|
||||
};
|
||||
const Default = {
|
||||
allowList: sanitizer_js.DefaultAllowlist,
|
||||
animation: true,
|
||||
boundary: 'clippingParents',
|
||||
container: false,
|
||||
customClass: '',
|
||||
delay: 0,
|
||||
fallbackPlacements: ['top', 'right', 'bottom', 'left'],
|
||||
html: false,
|
||||
offset: [0, 6],
|
||||
placement: 'top',
|
||||
popperConfig: null,
|
||||
sanitize: true,
|
||||
sanitizeFn: null,
|
||||
selector: false,
|
||||
template: '<div class="tooltip" role="tooltip">' + '<div class="tooltip-arrow"></div>' + '<div class="tooltip-inner"></div>' + '</div>',
|
||||
title: '',
|
||||
trigger: 'hover focus'
|
||||
};
|
||||
const DefaultType = {
|
||||
allowList: 'object',
|
||||
animation: 'boolean',
|
||||
boundary: '(string|element)',
|
||||
container: '(string|element|boolean)',
|
||||
customClass: '(string|function)',
|
||||
delay: '(number|object)',
|
||||
fallbackPlacements: 'array',
|
||||
html: 'boolean',
|
||||
offset: '(array|string|function)',
|
||||
placement: '(string|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
sanitize: 'boolean',
|
||||
sanitizeFn: '(null|function)',
|
||||
selector: '(string|boolean)',
|
||||
template: 'string',
|
||||
title: '(string|element|function)',
|
||||
trigger: 'string'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Tooltip extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
if (typeof Popper__namespace === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)');
|
||||
}
|
||||
super(element, config);
|
||||
|
||||
// Private
|
||||
this._isEnabled = true;
|
||||
this._timeout = 0;
|
||||
this._isHovered = null;
|
||||
this._activeTrigger = {};
|
||||
this._popper = null;
|
||||
this._templateFactory = null;
|
||||
this._newContent = null;
|
||||
|
||||
// Protected
|
||||
this.tip = null;
|
||||
this._setListeners();
|
||||
if (!this._config.selector) {
|
||||
this._fixTitle();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
enable() {
|
||||
this._isEnabled = true;
|
||||
}
|
||||
disable() {
|
||||
this._isEnabled = false;
|
||||
}
|
||||
toggleEnabled() {
|
||||
this._isEnabled = !this._isEnabled;
|
||||
}
|
||||
toggle() {
|
||||
if (!this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
this._activeTrigger.click = !this._activeTrigger.click;
|
||||
if (this._isShown()) {
|
||||
this._leave();
|
||||
return;
|
||||
}
|
||||
this._enter();
|
||||
}
|
||||
dispose() {
|
||||
clearTimeout(this._timeout);
|
||||
EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);
|
||||
if (this._element.getAttribute('data-bs-original-title')) {
|
||||
this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));
|
||||
}
|
||||
this._disposePopper();
|
||||
super.dispose();
|
||||
}
|
||||
show() {
|
||||
if (this._element.style.display === 'none') {
|
||||
throw new Error('Please use show on visible elements');
|
||||
}
|
||||
if (!(this._isWithContent() && this._isEnabled)) {
|
||||
return;
|
||||
}
|
||||
const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW));
|
||||
const shadowRoot = index_js.findShadowRoot(this._element);
|
||||
const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);
|
||||
if (showEvent.defaultPrevented || !isInTheDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: v6 remove this or make it optional
|
||||
this._disposePopper();
|
||||
const tip = this._getTipElement();
|
||||
this._element.setAttribute('aria-describedby', tip.getAttribute('id'));
|
||||
const {
|
||||
container
|
||||
} = this._config;
|
||||
if (!this._element.ownerDocument.documentElement.contains(this.tip)) {
|
||||
container.append(tip);
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));
|
||||
}
|
||||
this._popper = this._createPopper(tip);
|
||||
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) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.on(element, 'mouseover', index_js.noop);
|
||||
}
|
||||
}
|
||||
const complete = () => {
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN));
|
||||
if (this._isHovered === false) {
|
||||
this._leave();
|
||||
}
|
||||
this._isHovered = false;
|
||||
};
|
||||
this._queueCallback(complete, this.tip, this._isAnimated());
|
||||
}
|
||||
hide() {
|
||||
if (!this._isShown()) {
|
||||
return;
|
||||
}
|
||||
const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE));
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
const tip = this._getTipElement();
|
||||
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) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.off(element, 'mouseover', index_js.noop);
|
||||
}
|
||||
}
|
||||
this._activeTrigger[TRIGGER_CLICK] = false;
|
||||
this._activeTrigger[TRIGGER_FOCUS] = false;
|
||||
this._activeTrigger[TRIGGER_HOVER] = false;
|
||||
this._isHovered = null; // it is a trick to support manual triggering
|
||||
|
||||
const complete = () => {
|
||||
if (this._isWithActiveTrigger()) {
|
||||
return;
|
||||
}
|
||||
if (!this._isHovered) {
|
||||
this._disposePopper();
|
||||
}
|
||||
this._element.removeAttribute('aria-describedby');
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN));
|
||||
};
|
||||
this._queueCallback(complete, this.tip, this._isAnimated());
|
||||
}
|
||||
update() {
|
||||
if (this._popper) {
|
||||
this._popper.update();
|
||||
}
|
||||
}
|
||||
|
||||
// Protected
|
||||
_isWithContent() {
|
||||
return Boolean(this._getTitle());
|
||||
}
|
||||
_getTipElement() {
|
||||
if (!this.tip) {
|
||||
this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());
|
||||
}
|
||||
return this.tip;
|
||||
}
|
||||
_createTipElement(content) {
|
||||
const tip = this._getTemplateFactory(content).toHtml();
|
||||
|
||||
// TODO: remove this check in v6
|
||||
if (!tip) {
|
||||
return null;
|
||||
}
|
||||
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW);
|
||||
// TODO: v6 the following can be achieved with CSS only
|
||||
tip.classList.add(`bs-${this.constructor.NAME}-auto`);
|
||||
const tipId = index_js.getUID(this.constructor.NAME).toString();
|
||||
tip.setAttribute('id', tipId);
|
||||
if (this._isAnimated()) {
|
||||
tip.classList.add(CLASS_NAME_FADE);
|
||||
}
|
||||
return tip;
|
||||
}
|
||||
setContent(content) {
|
||||
this._newContent = content;
|
||||
if (this._isShown()) {
|
||||
this._disposePopper();
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
_getTemplateFactory(content) {
|
||||
if (this._templateFactory) {
|
||||
this._templateFactory.changeContent(content);
|
||||
} else {
|
||||
this._templateFactory = new TemplateFactory({
|
||||
...this._config,
|
||||
// the `content` var has to be after `this._config`
|
||||
// to override config.content in case of popover
|
||||
content,
|
||||
extraClass: this._resolvePossibleFunction(this._config.customClass)
|
||||
});
|
||||
}
|
||||
return this._templateFactory;
|
||||
}
|
||||
_getContentForTemplate() {
|
||||
return {
|
||||
[SELECTOR_TOOLTIP_INNER]: this._getTitle()
|
||||
};
|
||||
}
|
||||
_getTitle() {
|
||||
return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');
|
||||
}
|
||||
|
||||
// Private
|
||||
_initializeOnDelegatedTarget(event) {
|
||||
return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());
|
||||
}
|
||||
_isAnimated() {
|
||||
return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE);
|
||||
}
|
||||
_isShown() {
|
||||
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
_createPopper(tip) {
|
||||
const placement = index_js.execute(this._config.placement, [this, tip, this._element]);
|
||||
const attachment = AttachmentMap[placement.toUpperCase()];
|
||||
return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment));
|
||||
}
|
||||
_getOffset() {
|
||||
const {
|
||||
offset
|
||||
} = this._config;
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10));
|
||||
}
|
||||
if (typeof offset === 'function') {
|
||||
return popperData => offset(popperData, this._element);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
_resolvePossibleFunction(arg) {
|
||||
return index_js.execute(arg, [this._element]);
|
||||
}
|
||||
_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: 'preSetPlacement',
|
||||
enabled: true,
|
||||
phase: 'beforeMain',
|
||||
fn: data => {
|
||||
// Pre-set Popper's placement attribute in order to read the arrow sizes properly.
|
||||
// Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
|
||||
this._getTipElement().setAttribute('data-popper-placement', data.state.placement);
|
||||
}
|
||||
}]
|
||||
};
|
||||
return {
|
||||
...defaultBsPopperConfig,
|
||||
...index_js.execute(this._config.popperConfig, [defaultBsPopperConfig])
|
||||
};
|
||||
}
|
||||
_setListeners() {
|
||||
const triggers = this._config.trigger.split(' ');
|
||||
for (const trigger of triggers) {
|
||||
if (trigger === 'click') {
|
||||
EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {
|
||||
const context = this._initializeOnDelegatedTarget(event);
|
||||
context.toggle();
|
||||
});
|
||||
} else if (trigger !== TRIGGER_MANUAL) {
|
||||
const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN);
|
||||
const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT);
|
||||
EventHandler.on(this._element, eventIn, this._config.selector, event => {
|
||||
const context = this._initializeOnDelegatedTarget(event);
|
||||
context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;
|
||||
context._enter();
|
||||
});
|
||||
EventHandler.on(this._element, eventOut, this._config.selector, event => {
|
||||
const context = this._initializeOnDelegatedTarget(event);
|
||||
context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);
|
||||
context._leave();
|
||||
});
|
||||
}
|
||||
}
|
||||
this._hideModalHandler = () => {
|
||||
if (this._element) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);
|
||||
}
|
||||
_fixTitle() {
|
||||
const title = this._element.getAttribute('title');
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {
|
||||
this._element.setAttribute('aria-label', title);
|
||||
}
|
||||
this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility
|
||||
this._element.removeAttribute('title');
|
||||
}
|
||||
_enter() {
|
||||
if (this._isShown() || this._isHovered) {
|
||||
this._isHovered = true;
|
||||
return;
|
||||
}
|
||||
this._isHovered = true;
|
||||
this._setTimeout(() => {
|
||||
if (this._isHovered) {
|
||||
this.show();
|
||||
}
|
||||
}, this._config.delay.show);
|
||||
}
|
||||
_leave() {
|
||||
if (this._isWithActiveTrigger()) {
|
||||
return;
|
||||
}
|
||||
this._isHovered = false;
|
||||
this._setTimeout(() => {
|
||||
if (!this._isHovered) {
|
||||
this.hide();
|
||||
}
|
||||
}, this._config.delay.hide);
|
||||
}
|
||||
_setTimeout(handler, timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = setTimeout(handler, timeout);
|
||||
}
|
||||
_isWithActiveTrigger() {
|
||||
return Object.values(this._activeTrigger).includes(true);
|
||||
}
|
||||
_getConfig(config) {
|
||||
const dataAttributes = Manipulator.getDataAttributes(this._element);
|
||||
for (const dataAttribute of Object.keys(dataAttributes)) {
|
||||
if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {
|
||||
delete dataAttributes[dataAttribute];
|
||||
}
|
||||
}
|
||||
config = {
|
||||
...dataAttributes,
|
||||
...(typeof config === 'object' && config ? config : {})
|
||||
};
|
||||
config = this._mergeConfigObj(config);
|
||||
config = this._configAfterMerge(config);
|
||||
this._typeCheckConfig(config);
|
||||
return config;
|
||||
}
|
||||
_configAfterMerge(config) {
|
||||
config.container = config.container === false ? document.body : index_js.getElement(config.container);
|
||||
if (typeof config.delay === 'number') {
|
||||
config.delay = {
|
||||
show: config.delay,
|
||||
hide: config.delay
|
||||
};
|
||||
}
|
||||
if (typeof config.title === 'number') {
|
||||
config.title = config.title.toString();
|
||||
}
|
||||
if (typeof config.content === 'number') {
|
||||
config.content = config.content.toString();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
_getDelegateConfig() {
|
||||
const config = {};
|
||||
for (const [key, value] of Object.entries(this._config)) {
|
||||
if (this.constructor.Default[key] !== value) {
|
||||
config[key] = value;
|
||||
}
|
||||
}
|
||||
config.selector = false;
|
||||
config.trigger = 'manual';
|
||||
|
||||
// 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;
|
||||
}
|
||||
_disposePopper() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy();
|
||||
this._popper = null;
|
||||
}
|
||||
if (this.tip) {
|
||||
this.tip.remove();
|
||||
this.tip = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Tooltip.getOrCreateInstance(this, config);
|
||||
if (typeof config !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`);
|
||||
}
|
||||
data[config]();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
index_js.defineJQueryPlugin(Tooltip);
|
||||
|
||||
return Tooltip;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=tooltip.js.map
|
1
bootstrap/js/dist/tooltip.js.map
vendored
Normal file
1
bootstrap/js/dist/tooltip.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
139
bootstrap/js/dist/util/backdrop.js
vendored
Normal file
139
bootstrap/js/dist/util/backdrop.js
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*!
|
||||
* Bootstrap backdrop.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('./config.js'), require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../dom/event-handler', './config', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Backdrop = factory(global.EventHandler, global.Config, global.Index));
|
||||
})(this, (function (EventHandler, Config, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/backdrop.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'backdrop';
|
||||
const CLASS_NAME_FADE = 'fade';
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`;
|
||||
const Default = {
|
||||
className: 'modal-backdrop',
|
||||
clickCallback: null,
|
||||
isAnimated: false,
|
||||
isVisible: true,
|
||||
// if false, we use the backdrop helper without adding any element to the dom
|
||||
rootElement: 'body' // give the choice to place backdrop under different elements
|
||||
};
|
||||
const DefaultType = {
|
||||
className: 'string',
|
||||
clickCallback: '(function|null)',
|
||||
isAnimated: 'boolean',
|
||||
isVisible: 'boolean',
|
||||
rootElement: '(element|string)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Backdrop extends Config {
|
||||
constructor(config) {
|
||||
super();
|
||||
this._config = this._getConfig(config);
|
||||
this._isAppended = false;
|
||||
this._element = null;
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
show(callback) {
|
||||
if (!this._config.isVisible) {
|
||||
index_js.execute(callback);
|
||||
return;
|
||||
}
|
||||
this._append();
|
||||
const element = this._getElement();
|
||||
if (this._config.isAnimated) {
|
||||
index_js.reflow(element);
|
||||
}
|
||||
element.classList.add(CLASS_NAME_SHOW);
|
||||
this._emulateAnimation(() => {
|
||||
index_js.execute(callback);
|
||||
});
|
||||
}
|
||||
hide(callback) {
|
||||
if (!this._config.isVisible) {
|
||||
index_js.execute(callback);
|
||||
return;
|
||||
}
|
||||
this._getElement().classList.remove(CLASS_NAME_SHOW);
|
||||
this._emulateAnimation(() => {
|
||||
this.dispose();
|
||||
index_js.execute(callback);
|
||||
});
|
||||
}
|
||||
dispose() {
|
||||
if (!this._isAppended) {
|
||||
return;
|
||||
}
|
||||
EventHandler.off(this._element, EVENT_MOUSEDOWN);
|
||||
this._element.remove();
|
||||
this._isAppended = false;
|
||||
}
|
||||
|
||||
// Private
|
||||
_getElement() {
|
||||
if (!this._element) {
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.className = this._config.className;
|
||||
if (this._config.isAnimated) {
|
||||
backdrop.classList.add(CLASS_NAME_FADE);
|
||||
}
|
||||
this._element = backdrop;
|
||||
}
|
||||
return this._element;
|
||||
}
|
||||
_configAfterMerge(config) {
|
||||
// use getElement() with the default "body" to get a fresh Element on each instantiation
|
||||
config.rootElement = index_js.getElement(config.rootElement);
|
||||
return config;
|
||||
}
|
||||
_append() {
|
||||
if (this._isAppended) {
|
||||
return;
|
||||
}
|
||||
const element = this._getElement();
|
||||
this._config.rootElement.append(element);
|
||||
EventHandler.on(element, EVENT_MOUSEDOWN, () => {
|
||||
index_js.execute(this._config.clickCallback);
|
||||
});
|
||||
this._isAppended = true;
|
||||
}
|
||||
_emulateAnimation(callback) {
|
||||
index_js.executeAfterTransition(callback, this._getElement(), this._config.isAnimated);
|
||||
}
|
||||
}
|
||||
|
||||
return Backdrop;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=backdrop.js.map
|
1
bootstrap/js/dist/util/backdrop.js.map
vendored
Normal file
1
bootstrap/js/dist/util/backdrop.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
42
bootstrap/js/dist/util/component-functions.js
vendored
Normal file
42
bootstrap/js/dist/util/component-functions.js
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*!
|
||||
* Bootstrap component-functions.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('../dom/event-handler.js'), require('../dom/selector-engine.js'), require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['exports', '../dom/event-handler', '../dom/selector-engine', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ComponentFunctions = {}, global.EventHandler, global.SelectorEngine, global.Index));
|
||||
})(this, (function (exports, EventHandler, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/component-functions.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
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 (index_js.isDisabled(this)) {
|
||||
return;
|
||||
}
|
||||
const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);
|
||||
const instance = component.getOrCreateInstance(target);
|
||||
|
||||
// Method argument is left, for Alert and only, as it doesn't implement the 'hide' method
|
||||
instance[method]();
|
||||
});
|
||||
};
|
||||
|
||||
exports.enableDismissTrigger = enableDismissTrigger;
|
||||
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=component-functions.js.map
|
1
bootstrap/js/dist/util/component-functions.js.map
vendored
Normal file
1
bootstrap/js/dist/util/component-functions.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"component-functions.js","sources":["../../src/util/component-functions.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n"],"names":["enableDismissTrigger","component","method","clickEvent","EVENT_KEY","name","NAME","EventHandler","on","document","event","includes","tagName","preventDefault","isDisabled","target","SelectorEngine","getElementFromSelector","closest","instance","getOrCreateInstance"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;AAMMA,QAAAA,oBAAoB,GAAGA,CAACC,SAAS,EAAEC,MAAM,GAAG,MAAM,KAAK;EAC3D,EAAA,MAAMC,UAAU,GAAI,CAAA,aAAA,EAAeF,SAAS,CAACG,SAAU,CAAC,CAAA,CAAA;EACxD,EAAA,MAAMC,IAAI,GAAGJ,SAAS,CAACK,IAAI,CAAA;EAE3BC,EAAAA,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEN,UAAU,EAAG,CAAA,kBAAA,EAAoBE,IAAK,CAAA,EAAA,CAAG,EAAE,UAAUK,KAAK,EAAE;EACpF,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE,CAAA;EACxB,KAAA;EAEA,IAAA,IAAIC,mBAAU,CAAC,IAAI,CAAC,EAAE;EACpB,MAAA,OAAA;EACF,KAAA;EAEA,IAAA,MAAMC,MAAM,GAAGC,cAAc,CAACC,sBAAsB,CAAC,IAAI,CAAC,IAAI,IAAI,CAACC,OAAO,CAAE,CAAGb,CAAAA,EAAAA,IAAK,EAAC,CAAC,CAAA;EACtF,IAAA,MAAMc,QAAQ,GAAGlB,SAAS,CAACmB,mBAAmB,CAACL,MAAM,CAAC,CAAA;;EAEtD;EACAI,IAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE,CAAA;EACpB,GAAC,CAAC,CAAA;EACJ;;;;;;;;;;"}
|
68
bootstrap/js/dist/util/config.js
vendored
Normal file
68
bootstrap/js/dist/util/config.js
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*!
|
||||
* Bootstrap config.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/manipulator.js'), require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../dom/manipulator', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Config = factory(global.Manipulator, global.Index));
|
||||
})(this, (function (Manipulator, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/config.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Config {
|
||||
// Getters
|
||||
static get Default() {
|
||||
return {};
|
||||
}
|
||||
static get DefaultType() {
|
||||
return {};
|
||||
}
|
||||
static get NAME() {
|
||||
throw new Error('You have to implement the static method "NAME", for each component!');
|
||||
}
|
||||
_getConfig(config) {
|
||||
config = this._mergeConfigObj(config);
|
||||
config = this._configAfterMerge(config);
|
||||
this._typeCheckConfig(config);
|
||||
return config;
|
||||
}
|
||||
_configAfterMerge(config) {
|
||||
return config;
|
||||
}
|
||||
_mergeConfigObj(config, element) {
|
||||
const jsonConfig = index_js.isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse
|
||||
|
||||
return {
|
||||
...this.constructor.Default,
|
||||
...(typeof jsonConfig === 'object' ? jsonConfig : {}),
|
||||
...(index_js.isElement(element) ? Manipulator.getDataAttributes(element) : {}),
|
||||
...(typeof config === 'object' ? config : {})
|
||||
};
|
||||
}
|
||||
_typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
|
||||
for (const [property, expectedTypes] of Object.entries(configTypes)) {
|
||||
const value = config[property];
|
||||
const valueType = index_js.isElement(value) ? 'element' : index_js.toType(value);
|
||||
if (!new RegExp(expectedTypes).test(valueType)) {
|
||||
throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Config;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=config.js.map
|
1
bootstrap/js/dist/util/config.js.map
vendored
Normal file
1
bootstrap/js/dist/util/config.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"config.js","sources":["../../src/util/config.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport Manipulator from '../dom/manipulator.js'\nimport { isElement, toType } from './index.js'\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {}\n }\n\n static get DefaultType() {\n return {}\n }\n\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!')\n }\n\n _getConfig(config) {\n config = this._mergeConfigObj(config)\n config = this._configAfterMerge(config)\n this._typeCheckConfig(config)\n return config\n }\n\n _configAfterMerge(config) {\n return config\n }\n\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n }\n }\n\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property]\n const valueType = isElement(value) ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(\n `${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`\n )\n }\n }\n }\n}\n\nexport default Config\n"],"names":["Config","Default","DefaultType","NAME","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","element","jsonConfig","isElement","Manipulator","getDataAttribute","constructor","getDataAttributes","configTypes","property","expectedTypes","Object","entries","value","valueType","toType","RegExp","test","TypeError","toUpperCase"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAKA;EACA;EACA;;EAEA,MAAMA,MAAM,CAAC;EACX;IACA,WAAWC,OAAOA,GAAG;EACnB,IAAA,OAAO,EAAE,CAAA;EACX,GAAA;IAEA,WAAWC,WAAWA,GAAG;EACvB,IAAA,OAAO,EAAE,CAAA;EACX,GAAA;IAEA,WAAWC,IAAIA,GAAG;EAChB,IAAA,MAAM,IAAIC,KAAK,CAAC,qEAAqE,CAAC,CAAA;EACxF,GAAA;IAEAC,UAAUA,CAACC,MAAM,EAAE;EACjBA,IAAAA,MAAM,GAAG,IAAI,CAACC,eAAe,CAACD,MAAM,CAAC,CAAA;EACrCA,IAAAA,MAAM,GAAG,IAAI,CAACE,iBAAiB,CAACF,MAAM,CAAC,CAAA;EACvC,IAAA,IAAI,CAACG,gBAAgB,CAACH,MAAM,CAAC,CAAA;EAC7B,IAAA,OAAOA,MAAM,CAAA;EACf,GAAA;IAEAE,iBAAiBA,CAACF,MAAM,EAAE;EACxB,IAAA,OAAOA,MAAM,CAAA;EACf,GAAA;EAEAC,EAAAA,eAAeA,CAACD,MAAM,EAAEI,OAAO,EAAE;EAC/B,IAAA,MAAMC,UAAU,GAAGC,kBAAS,CAACF,OAAO,CAAC,GAAGG,WAAW,CAACC,gBAAgB,CAACJ,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC;;MAE7F,OAAO;EACL,MAAA,GAAG,IAAI,CAACK,WAAW,CAACd,OAAO;QAC3B,IAAI,OAAOU,UAAU,KAAK,QAAQ,GAAGA,UAAU,GAAG,EAAE;EACpD,MAAA,IAAIC,kBAAS,CAACF,OAAO,CAAC,GAAGG,WAAW,CAACG,iBAAiB,CAACN,OAAO,CAAC,GAAG,EAAE;QACpE,IAAI,OAAOJ,MAAM,KAAK,QAAQ,GAAGA,MAAM,GAAG,EAAE;OAC7C,CAAA;EACH,GAAA;IAEAG,gBAAgBA,CAACH,MAAM,EAAEW,WAAW,GAAG,IAAI,CAACF,WAAW,CAACb,WAAW,EAAE;EACnE,IAAA,KAAK,MAAM,CAACgB,QAAQ,EAAEC,aAAa,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACJ,WAAW,CAAC,EAAE;EACnE,MAAA,MAAMK,KAAK,GAAGhB,MAAM,CAACY,QAAQ,CAAC,CAAA;EAC9B,MAAA,MAAMK,SAAS,GAAGX,kBAAS,CAACU,KAAK,CAAC,GAAG,SAAS,GAAGE,eAAM,CAACF,KAAK,CAAC,CAAA;QAE9D,IAAI,CAAC,IAAIG,MAAM,CAACN,aAAa,CAAC,CAACO,IAAI,CAACH,SAAS,CAAC,EAAE;UAC9C,MAAM,IAAII,SAAS,CAChB,CAAA,EAAE,IAAI,CAACZ,WAAW,CAACZ,IAAI,CAACyB,WAAW,EAAG,aAAYV,QAAS,CAAA,iBAAA,EAAmBK,SAAU,CAAuBJ,qBAAAA,EAAAA,aAAc,IAChI,CAAC,CAAA;EACH,OAAA;EACF,KAAA;EACF,GAAA;EACF;;;;;;;;"}
|
113
bootstrap/js/dist/util/focustrap.js
vendored
Normal file
113
bootstrap/js/dist/util/focustrap.js
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*!
|
||||
* Bootstrap focustrap.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('../dom/selector-engine.js'), require('./config.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../dom/event-handler', '../dom/selector-engine', './config'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Focustrap = factory(global.EventHandler, global.SelectorEngine, global.Config));
|
||||
})(this, (function (EventHandler, SelectorEngine, Config) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/focustrap.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
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';
|
||||
const Default = {
|
||||
autofocus: true,
|
||||
trapElement: null // The element to trap focus inside of
|
||||
};
|
||||
const DefaultType = {
|
||||
autofocus: 'boolean',
|
||||
trapElement: 'element'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class FocusTrap extends Config {
|
||||
constructor(config) {
|
||||
super();
|
||||
this._config = this._getConfig(config);
|
||||
this._isActive = false;
|
||||
this._lastTabNavDirection = null;
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
activate() {
|
||||
if (this._isActive) {
|
||||
return;
|
||||
}
|
||||
if (this._config.autofocus) {
|
||||
this._config.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 {
|
||||
trapElement
|
||||
} = this._config;
|
||||
if (event.target === document || event.target === trapElement || trapElement.contains(event.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;
|
||||
}
|
||||
}
|
||||
|
||||
return FocusTrap;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=focustrap.js.map
|
1
bootstrap/js/dist/util/focustrap.js.map
vendored
Normal file
1
bootstrap/js/dist/util/focustrap.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
281
bootstrap/js/dist/util/index.js
vendored
Normal file
281
bootstrap/js/dist/util/index.js
vendored
Normal file
|
@ -0,0 +1,281 @@
|
|||
/*!
|
||||
* Bootstrap index.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Index = {}));
|
||||
})(this, (function (exports) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap 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';
|
||||
|
||||
/**
|
||||
* Properly escape IDs selectors to handle weird IDs
|
||||
* @param {string} selector
|
||||
* @returns {string}
|
||||
*/
|
||||
const parseSelector = selector => {
|
||||
if (selector && window.CSS && window.CSS.escape) {
|
||||
// document.querySelector needs escaping to handle IDs (html5+) containing for instance /
|
||||
selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`);
|
||||
}
|
||||
return selector;
|
||||
};
|
||||
|
||||
// Shout-out Angus Croll (https://goo.gl/pxwQGp)
|
||||
const toType = object => {
|
||||
if (object === null || object === undefined) {
|
||||
return `${object}`;
|
||||
}
|
||||
return Object.prototype.toString.call(object).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 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 = object => {
|
||||
if (!object || typeof object !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (typeof object.jquery !== 'undefined') {
|
||||
object = object[0];
|
||||
}
|
||||
return typeof object.nodeType !== 'undefined';
|
||||
};
|
||||
const getElement = object => {
|
||||
// it's a jQuery object or a node element
|
||||
if (isElement(object)) {
|
||||
return object.jquery ? object[0] : object;
|
||||
}
|
||||
if (typeof object === 'string' && object.length > 0) {
|
||||
return document.querySelector(parseSelector(object));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const isVisible = element => {
|
||||
if (!isElement(element) || element.getClientRects().length === 0) {
|
||||
return false;
|
||||
}
|
||||
const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';
|
||||
// Handle `details` element as its content may falsie appear visible when it is closed
|
||||
const closedDetails = element.closest('details:not([open])');
|
||||
if (!closedDetails) {
|
||||
return elementIsVisible;
|
||||
}
|
||||
if (closedDetails !== element) {
|
||||
const summary = element.closest('summary');
|
||||
if (summary && summary.parentNode !== closedDetails) {
|
||||
return false;
|
||||
}
|
||||
if (summary === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return elementIsVisible;
|
||||
};
|
||||
const isDisabled = element => {
|
||||
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 => {
|
||||
element.offsetHeight; // eslint-disable-line no-unused-expressions
|
||||
};
|
||||
const getjQuery = () => {
|
||||
if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
|
||||
return window.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', () => {
|
||||
for (const callback of DOMContentLoadedCallbacks) {
|
||||
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 = (possibleCallback, args = [], defaultValue = possibleCallback) => {
|
||||
return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;
|
||||
};
|
||||
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) => {
|
||||
const listLength = list.length;
|
||||
let index = list.indexOf(activeElement);
|
||||
|
||||
// if the element does not exist in the list return an element
|
||||
// depending on the direction and if cycle is allowed
|
||||
if (index === -1) {
|
||||
return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];
|
||||
}
|
||||
index += shouldGetNext ? 1 : -1;
|
||||
if (isCycleAllowed) {
|
||||
index = (index + listLength) % listLength;
|
||||
}
|
||||
return list[Math.max(0, Math.min(index, listLength - 1))];
|
||||
};
|
||||
|
||||
exports.defineJQueryPlugin = defineJQueryPlugin;
|
||||
exports.execute = execute;
|
||||
exports.executeAfterTransition = executeAfterTransition;
|
||||
exports.findShadowRoot = findShadowRoot;
|
||||
exports.getElement = getElement;
|
||||
exports.getNextActiveElement = getNextActiveElement;
|
||||
exports.getTransitionDurationFromElement = getTransitionDurationFromElement;
|
||||
exports.getUID = getUID;
|
||||
exports.getjQuery = getjQuery;
|
||||
exports.isDisabled = isDisabled;
|
||||
exports.isElement = isElement;
|
||||
exports.isRTL = isRTL;
|
||||
exports.isVisible = isVisible;
|
||||
exports.noop = noop;
|
||||
exports.onDOMContentLoaded = onDOMContentLoaded;
|
||||
exports.parseSelector = parseSelector;
|
||||
exports.reflow = reflow;
|
||||
exports.toType = toType;
|
||||
exports.triggerTransitionEnd = triggerTransitionEnd;
|
||||
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=index.js.map
|
1
bootstrap/js/dist/util/index.js.map
vendored
Normal file
1
bootstrap/js/dist/util/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
114
bootstrap/js/dist/util/sanitizer.js
vendored
Normal file
114
bootstrap/js/dist/util/sanitizer.js
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*!
|
||||
* Bootstrap sanitizer.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Sanitizer = {}));
|
||||
})(this, (function (exports) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/sanitizer.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// js-docs-start allow-list
|
||||
const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
|
||||
const DefaultAllowlist = {
|
||||
// Global attributes allowed on any supplied element below.
|
||||
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
|
||||
a: ['target', 'href', 'title', 'rel'],
|
||||
area: [],
|
||||
b: [],
|
||||
br: [],
|
||||
col: [],
|
||||
code: [],
|
||||
dd: [],
|
||||
div: [],
|
||||
dl: [],
|
||||
dt: [],
|
||||
em: [],
|
||||
hr: [],
|
||||
h1: [],
|
||||
h2: [],
|
||||
h3: [],
|
||||
h4: [],
|
||||
h5: [],
|
||||
h6: [],
|
||||
i: [],
|
||||
img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
|
||||
li: [],
|
||||
ol: [],
|
||||
p: [],
|
||||
pre: [],
|
||||
s: [],
|
||||
small: [],
|
||||
span: [],
|
||||
sub: [],
|
||||
sup: [],
|
||||
strong: [],
|
||||
u: [],
|
||||
ul: []
|
||||
};
|
||||
// js-docs-end allow-list
|
||||
|
||||
const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);
|
||||
|
||||
/**
|
||||
* A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
|
||||
* contexts.
|
||||
*
|
||||
* Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
|
||||
*/
|
||||
// eslint-disable-next-line unicorn/better-regex
|
||||
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;
|
||||
const allowedAttribute = (attribute, allowedAttributeList) => {
|
||||
const attributeName = attribute.nodeName.toLowerCase();
|
||||
if (allowedAttributeList.includes(attributeName)) {
|
||||
if (uriAttributes.has(attributeName)) {
|
||||
return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if a regular expression validates the attribute.
|
||||
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));
|
||||
};
|
||||
function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
|
||||
if (!unsafeHtml.length) {
|
||||
return unsafeHtml;
|
||||
}
|
||||
if (sanitizeFunction && typeof sanitizeFunction === 'function') {
|
||||
return sanitizeFunction(unsafeHtml);
|
||||
}
|
||||
const domParser = new window.DOMParser();
|
||||
const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');
|
||||
const elements = [].concat(...createdDocument.body.querySelectorAll('*'));
|
||||
for (const element of elements) {
|
||||
const elementName = element.nodeName.toLowerCase();
|
||||
if (!Object.keys(allowList).includes(elementName)) {
|
||||
element.remove();
|
||||
continue;
|
||||
}
|
||||
const attributeList = [].concat(...element.attributes);
|
||||
const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);
|
||||
for (const attribute of attributeList) {
|
||||
if (!allowedAttribute(attribute, allowedAttributes)) {
|
||||
element.removeAttribute(attribute.nodeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createdDocument.body.innerHTML;
|
||||
}
|
||||
|
||||
exports.DefaultAllowlist = DefaultAllowlist;
|
||||
exports.sanitizeHtml = sanitizeHtml;
|
||||
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=sanitizer.js.map
|
1
bootstrap/js/dist/util/sanitizer.js.map
vendored
Normal file
1
bootstrap/js/dist/util/sanitizer.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
113
bootstrap/js/dist/util/scrollbar.js
vendored
Normal file
113
bootstrap/js/dist/util/scrollbar.js
vendored
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*!
|
||||
* Bootstrap scrollbar.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/manipulator.js'), require('../dom/selector-engine.js'), require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../dom/manipulator', '../dom/selector-engine', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Scrollbar = factory(global.Manipulator, global.SelectorEngine, global.Index));
|
||||
})(this, (function (Manipulator, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/scrollBar.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';
|
||||
const SELECTOR_STICKY_CONTENT = '.sticky-top';
|
||||
const PROPERTY_PADDING = 'padding-right';
|
||||
const PROPERTY_MARGIN = 'margin-right';
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class ScrollBarHelper {
|
||||
constructor() {
|
||||
this._element = document.body;
|
||||
}
|
||||
|
||||
// Public
|
||||
getWidth() {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
|
||||
const documentWidth = document.documentElement.clientWidth;
|
||||
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, PROPERTY_PADDING, calculatedValue => calculatedValue + width);
|
||||
// trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
|
||||
this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);
|
||||
this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);
|
||||
}
|
||||
reset() {
|
||||
this._resetElementAttributes(this._element, 'overflow');
|
||||
this._resetElementAttributes(this._element, PROPERTY_PADDING);
|
||||
this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);
|
||||
this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);
|
||||
}
|
||||
isOverflowing() {
|
||||
return this.getWidth() > 0;
|
||||
}
|
||||
|
||||
// Private
|
||||
_disableOverFlow() {
|
||||
this._saveInitialAttribute(this._element, 'overflow');
|
||||
this._element.style.overflow = 'hidden';
|
||||
}
|
||||
_setElementAttributes(selector, styleProperty, callback) {
|
||||
const scrollbarWidth = this.getWidth();
|
||||
const manipulationCallBack = element => {
|
||||
if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
|
||||
return;
|
||||
}
|
||||
this._saveInitialAttribute(element, styleProperty);
|
||||
const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);
|
||||
element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);
|
||||
};
|
||||
this._applyManipulationCallback(selector, manipulationCallBack);
|
||||
}
|
||||
_saveInitialAttribute(element, styleProperty) {
|
||||
const actualValue = element.style.getPropertyValue(styleProperty);
|
||||
if (actualValue) {
|
||||
Manipulator.setDataAttribute(element, styleProperty, actualValue);
|
||||
}
|
||||
}
|
||||
_resetElementAttributes(selector, styleProperty) {
|
||||
const manipulationCallBack = element => {
|
||||
const value = Manipulator.getDataAttribute(element, styleProperty);
|
||||
// We only want to remove the property if the value is `null`; the value can also be zero
|
||||
if (value === null) {
|
||||
element.style.removeProperty(styleProperty);
|
||||
return;
|
||||
}
|
||||
Manipulator.removeDataAttribute(element, styleProperty);
|
||||
element.style.setProperty(styleProperty, value);
|
||||
};
|
||||
this._applyManipulationCallback(selector, manipulationCallBack);
|
||||
}
|
||||
_applyManipulationCallback(selector, callBack) {
|
||||
if (index_js.isElement(selector)) {
|
||||
callBack(selector);
|
||||
return;
|
||||
}
|
||||
for (const sel of SelectorEngine.find(selector, this._element)) {
|
||||
callBack(sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ScrollBarHelper;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=scrollbar.js.map
|
1
bootstrap/js/dist/util/scrollbar.js.map
vendored
Normal file
1
bootstrap/js/dist/util/scrollbar.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
135
bootstrap/js/dist/util/swipe.js
vendored
Normal file
135
bootstrap/js/dist/util/swipe.js
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*!
|
||||
* Bootstrap swipe.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/event-handler.js'), require('./config.js'), require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../dom/event-handler', './config', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Swipe = factory(global.EventHandler, global.Config, global.Index));
|
||||
})(this, (function (EventHandler, Config, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/swipe.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'swipe';
|
||||
const EVENT_KEY = '.bs.swipe';
|
||||
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`;
|
||||
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`;
|
||||
const EVENT_TOUCHEND = `touchend${EVENT_KEY}`;
|
||||
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`;
|
||||
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`;
|
||||
const POINTER_TYPE_TOUCH = 'touch';
|
||||
const POINTER_TYPE_PEN = 'pen';
|
||||
const CLASS_NAME_POINTER_EVENT = 'pointer-event';
|
||||
const SWIPE_THRESHOLD = 40;
|
||||
const Default = {
|
||||
endCallback: null,
|
||||
leftCallback: null,
|
||||
rightCallback: null
|
||||
};
|
||||
const DefaultType = {
|
||||
endCallback: '(function|null)',
|
||||
leftCallback: '(function|null)',
|
||||
rightCallback: '(function|null)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Swipe extends Config {
|
||||
constructor(element, config) {
|
||||
super();
|
||||
this._element = element;
|
||||
if (!element || !Swipe.isSupported()) {
|
||||
return;
|
||||
}
|
||||
this._config = this._getConfig(config);
|
||||
this._deltaX = 0;
|
||||
this._supportPointerEvents = Boolean(window.PointerEvent);
|
||||
this._initEvents();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
dispose() {
|
||||
EventHandler.off(this._element, EVENT_KEY);
|
||||
}
|
||||
|
||||
// Private
|
||||
_start(event) {
|
||||
if (!this._supportPointerEvents) {
|
||||
this._deltaX = event.touches[0].clientX;
|
||||
return;
|
||||
}
|
||||
if (this._eventIsPointerPenTouch(event)) {
|
||||
this._deltaX = event.clientX;
|
||||
}
|
||||
}
|
||||
_end(event) {
|
||||
if (this._eventIsPointerPenTouch(event)) {
|
||||
this._deltaX = event.clientX - this._deltaX;
|
||||
}
|
||||
this._handleSwipe();
|
||||
index_js.execute(this._config.endCallback);
|
||||
}
|
||||
_move(event) {
|
||||
this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;
|
||||
}
|
||||
_handleSwipe() {
|
||||
const absDeltaX = Math.abs(this._deltaX);
|
||||
if (absDeltaX <= SWIPE_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
const direction = absDeltaX / this._deltaX;
|
||||
this._deltaX = 0;
|
||||
if (!direction) {
|
||||
return;
|
||||
}
|
||||
index_js.execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);
|
||||
}
|
||||
_initEvents() {
|
||||
if (this._supportPointerEvents) {
|
||||
EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));
|
||||
EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));
|
||||
this._element.classList.add(CLASS_NAME_POINTER_EVENT);
|
||||
} else {
|
||||
EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));
|
||||
EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));
|
||||
EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));
|
||||
}
|
||||
}
|
||||
_eventIsPointerPenTouch(event) {
|
||||
return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);
|
||||
}
|
||||
|
||||
// Static
|
||||
static isSupported() {
|
||||
return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return Swipe;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=swipe.js.map
|
1
bootstrap/js/dist/util/swipe.js.map
vendored
Normal file
1
bootstrap/js/dist/util/swipe.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
151
bootstrap/js/dist/util/template-factory.js
vendored
Normal file
151
bootstrap/js/dist/util/template-factory.js
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*!
|
||||
* Bootstrap template-factory.js v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../dom/selector-engine.js'), require('./config.js'), require('./sanitizer.js'), require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['../dom/selector-engine', './config', './sanitizer', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TemplateFactory = factory(global.SelectorEngine, global.Config, global.Sanitizer, global.Index));
|
||||
})(this, (function (SelectorEngine, Config, sanitizer_js, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/template-factory.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'TemplateFactory';
|
||||
const Default = {
|
||||
allowList: sanitizer_js.DefaultAllowlist,
|
||||
content: {},
|
||||
// { selector : text , selector2 : text2 , }
|
||||
extraClass: '',
|
||||
html: false,
|
||||
sanitize: true,
|
||||
sanitizeFn: null,
|
||||
template: '<div></div>'
|
||||
};
|
||||
const DefaultType = {
|
||||
allowList: 'object',
|
||||
content: 'object',
|
||||
extraClass: '(string|function)',
|
||||
html: 'boolean',
|
||||
sanitize: 'boolean',
|
||||
sanitizeFn: '(null|function)',
|
||||
template: 'string'
|
||||
};
|
||||
const DefaultContentType = {
|
||||
entry: '(string|element|function|null)',
|
||||
selector: '(string|element)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class TemplateFactory extends Config {
|
||||
constructor(config) {
|
||||
super();
|
||||
this._config = this._getConfig(config);
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
getContent() {
|
||||
return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);
|
||||
}
|
||||
hasContent() {
|
||||
return this.getContent().length > 0;
|
||||
}
|
||||
changeContent(content) {
|
||||
this._checkContent(content);
|
||||
this._config.content = {
|
||||
...this._config.content,
|
||||
...content
|
||||
};
|
||||
return this;
|
||||
}
|
||||
toHtml() {
|
||||
const templateWrapper = document.createElement('div');
|
||||
templateWrapper.innerHTML = this._maybeSanitize(this._config.template);
|
||||
for (const [selector, text] of Object.entries(this._config.content)) {
|
||||
this._setContent(templateWrapper, text, selector);
|
||||
}
|
||||
const template = templateWrapper.children[0];
|
||||
const extraClass = this._resolvePossibleFunction(this._config.extraClass);
|
||||
if (extraClass) {
|
||||
template.classList.add(...extraClass.split(' '));
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
// Private
|
||||
_typeCheckConfig(config) {
|
||||
super._typeCheckConfig(config);
|
||||
this._checkContent(config.content);
|
||||
}
|
||||
_checkContent(arg) {
|
||||
for (const [selector, content] of Object.entries(arg)) {
|
||||
super._typeCheckConfig({
|
||||
selector,
|
||||
entry: content
|
||||
}, DefaultContentType);
|
||||
}
|
||||
}
|
||||
_setContent(template, content, selector) {
|
||||
const templateElement = SelectorEngine.findOne(selector, template);
|
||||
if (!templateElement) {
|
||||
return;
|
||||
}
|
||||
content = this._resolvePossibleFunction(content);
|
||||
if (!content) {
|
||||
templateElement.remove();
|
||||
return;
|
||||
}
|
||||
if (index_js.isElement(content)) {
|
||||
this._putElementInTemplate(index_js.getElement(content), templateElement);
|
||||
return;
|
||||
}
|
||||
if (this._config.html) {
|
||||
templateElement.innerHTML = this._maybeSanitize(content);
|
||||
return;
|
||||
}
|
||||
templateElement.textContent = content;
|
||||
}
|
||||
_maybeSanitize(arg) {
|
||||
return this._config.sanitize ? sanitizer_js.sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;
|
||||
}
|
||||
_resolvePossibleFunction(arg) {
|
||||
return index_js.execute(arg, [this]);
|
||||
}
|
||||
_putElementInTemplate(element, templateElement) {
|
||||
if (this._config.html) {
|
||||
templateElement.innerHTML = '';
|
||||
templateElement.append(element);
|
||||
return;
|
||||
}
|
||||
templateElement.textContent = element.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
return TemplateFactory;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=template-factory.js.map
|
1
bootstrap/js/dist/util/template-factory.js.map
vendored
Normal file
1
bootstrap/js/dist/util/template-factory.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
19
bootstrap/js/index.esm.js
Normal file
19
bootstrap/js/index.esm.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap index.esm.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export { default as Alert } from './src/alert.js'
|
||||
export { default as Button } from './src/button.js'
|
||||
export { default as Carousel } from './src/carousel.js'
|
||||
export { default as Collapse } from './src/collapse.js'
|
||||
export { default as Dropdown } from './src/dropdown.js'
|
||||
export { default as Modal } from './src/modal.js'
|
||||
export { default as Offcanvas } from './src/offcanvas.js'
|
||||
export { default as Popover } from './src/popover.js'
|
||||
export { default as ScrollSpy } from './src/scrollspy.js'
|
||||
export { default as Tab } from './src/tab.js'
|
||||
export { default as Toast } from './src/toast.js'
|
||||
export { default as Tooltip } from './src/tooltip.js'
|
34
bootstrap/js/index.umd.js
Normal file
34
bootstrap/js/index.umd.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap index.umd.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import Alert from './src/alert.js'
|
||||
import Button from './src/button.js'
|
||||
import Carousel from './src/carousel.js'
|
||||
import Collapse from './src/collapse.js'
|
||||
import Dropdown from './src/dropdown.js'
|
||||
import Modal from './src/modal.js'
|
||||
import Offcanvas from './src/offcanvas.js'
|
||||
import Popover from './src/popover.js'
|
||||
import ScrollSpy from './src/scrollspy.js'
|
||||
import Tab from './src/tab.js'
|
||||
import Toast from './src/toast.js'
|
||||
import Tooltip from './src/tooltip.js'
|
||||
|
||||
export default {
|
||||
Alert,
|
||||
Button,
|
||||
Carousel,
|
||||
Collapse,
|
||||
Dropdown,
|
||||
Modal,
|
||||
Offcanvas,
|
||||
Popover,
|
||||
ScrollSpy,
|
||||
Tab,
|
||||
Toast,
|
||||
Tooltip
|
||||
}
|
87
bootstrap/js/src/alert.js
Normal file
87
bootstrap/js/src/alert.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap alert.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import { enableDismissTrigger } from './util/component-functions.js'
|
||||
import { defineJQueryPlugin } from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'alert'
|
||||
const DATA_KEY = 'bs.alert'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
|
||||
const EVENT_CLOSE = `close${EVENT_KEY}`
|
||||
const EVENT_CLOSED = `closed${EVENT_KEY}`
|
||||
const CLASS_NAME_FADE = 'fade'
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Alert extends BaseComponent {
|
||||
// Getters
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
close() {
|
||||
const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)
|
||||
|
||||
if (closeEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
this._element.classList.remove(CLASS_NAME_SHOW)
|
||||
|
||||
const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)
|
||||
this._queueCallback(() => this._destroyElement(), this._element, isAnimated)
|
||||
}
|
||||
|
||||
// Private
|
||||
_destroyElement() {
|
||||
this._element.remove()
|
||||
EventHandler.trigger(this._element, EVENT_CLOSED)
|
||||
this.dispose()
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Alert.getOrCreateInstance(this)
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
enableDismissTrigger(Alert, 'close')
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Alert)
|
||||
|
||||
export default Alert
|
85
bootstrap/js/src/base-component.js
Normal file
85
bootstrap/js/src/base-component.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap base-component.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import Data from './dom/data.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import Config from './util/config.js'
|
||||
import { executeAfterTransition, getElement } from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const VERSION = '5.3.3'
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class BaseComponent extends Config {
|
||||
constructor(element, config) {
|
||||
super()
|
||||
|
||||
element = getElement(element)
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
this._element = element
|
||||
this._config = this._getConfig(config)
|
||||
|
||||
Data.set(this._element, this.constructor.DATA_KEY, this)
|
||||
}
|
||||
|
||||
// Public
|
||||
dispose() {
|
||||
Data.remove(this._element, this.constructor.DATA_KEY)
|
||||
EventHandler.off(this._element, this.constructor.EVENT_KEY)
|
||||
|
||||
for (const propertyName of Object.getOwnPropertyNames(this)) {
|
||||
this[propertyName] = null
|
||||
}
|
||||
}
|
||||
|
||||
_queueCallback(callback, element, isAnimated = true) {
|
||||
executeAfterTransition(callback, element, isAnimated)
|
||||
}
|
||||
|
||||
_getConfig(config) {
|
||||
config = this._mergeConfigObj(config, this._element)
|
||||
config = this._configAfterMerge(config)
|
||||
this._typeCheckConfig(config)
|
||||
return config
|
||||
}
|
||||
|
||||
// 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 DATA_KEY() {
|
||||
return `bs.${this.NAME}`
|
||||
}
|
||||
|
||||
static get EVENT_KEY() {
|
||||
return `.${this.DATA_KEY}`
|
||||
}
|
||||
|
||||
static eventName(name) {
|
||||
return `${name}${this.EVENT_KEY}`
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseComponent
|
72
bootstrap/js/src/button.js
Normal file
72
bootstrap/js/src/button.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap button.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import { defineJQueryPlugin } from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'button'
|
||||
const DATA_KEY = 'bs.button'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
|
||||
const CLASS_NAME_ACTIVE = 'active'
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="button"]'
|
||||
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Button extends BaseComponent {
|
||||
// Getters
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
// 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) {
|
||||
return this.each(function () {
|
||||
const data = Button.getOrCreateInstance(this)
|
||||
|
||||
if (config === 'toggle') {
|
||||
data[config]()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {
|
||||
event.preventDefault()
|
||||
|
||||
const button = event.target.closest(SELECTOR_DATA_TOGGLE)
|
||||
const data = Button.getOrCreateInstance(button)
|
||||
|
||||
data.toggle()
|
||||
})
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Button)
|
||||
|
||||
export default Button
|
474
bootstrap/js/src/carousel.js
Normal file
474
bootstrap/js/src/carousel.js
Normal file
|
@ -0,0 +1,474 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap carousel.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import Manipulator from './dom/manipulator.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
getNextActiveElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
reflow,
|
||||
triggerTransitionEnd
|
||||
} from './util/index.js'
|
||||
import Swipe from './util/swipe.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'carousel'
|
||||
const DATA_KEY = 'bs.carousel'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
|
||||
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 ORDER_NEXT = 'next'
|
||||
const ORDER_PREV = 'prev'
|
||||
const DIRECTION_LEFT = 'left'
|
||||
const DIRECTION_RIGHT = 'right'
|
||||
|
||||
const EVENT_SLIDE = `slide${EVENT_KEY}`
|
||||
const EVENT_SLID = `slid${EVENT_KEY}`
|
||||
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
|
||||
const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
|
||||
const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`
|
||||
const EVENT_DRAG_START = `dragstart${EVENT_KEY}`
|
||||
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
|
||||
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_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 SELECTOR_ACTIVE = '.active'
|
||||
const SELECTOR_ITEM = '.carousel-item'
|
||||
const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM
|
||||
const SELECTOR_ITEM_IMG = '.carousel-item img'
|
||||
const SELECTOR_INDICATORS = '.carousel-indicators'
|
||||
const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'
|
||||
const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'
|
||||
|
||||
const KEY_TO_DIRECTION = {
|
||||
[ARROW_LEFT_KEY]: DIRECTION_RIGHT,
|
||||
[ARROW_RIGHT_KEY]: DIRECTION_LEFT
|
||||
}
|
||||
|
||||
const Default = {
|
||||
interval: 5000,
|
||||
keyboard: true,
|
||||
pause: 'hover',
|
||||
ride: false,
|
||||
touch: true,
|
||||
wrap: true
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
interval: '(number|boolean)', // TODO:v6 remove boolean support
|
||||
keyboard: 'boolean',
|
||||
pause: '(string|boolean)',
|
||||
ride: '(boolean|string)',
|
||||
touch: 'boolean',
|
||||
wrap: 'boolean'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Carousel extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._interval = null
|
||||
this._activeElement = null
|
||||
this._isSliding = false
|
||||
this.touchTimeout = null
|
||||
this._swipeHelper = null
|
||||
|
||||
this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)
|
||||
this._addEventListeners()
|
||||
|
||||
if (this._config.ride === CLASS_NAME_CAROUSEL) {
|
||||
this.cycle()
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
next() {
|
||||
this._slide(ORDER_NEXT)
|
||||
}
|
||||
|
||||
nextWhenVisible() {
|
||||
// FIXME TODO use `document.visibilityState`
|
||||
// Don't call next when the page isn't visible
|
||||
// or the carousel or its parent isn't visible
|
||||
if (!document.hidden && isVisible(this._element)) {
|
||||
this.next()
|
||||
}
|
||||
}
|
||||
|
||||
prev() {
|
||||
this._slide(ORDER_PREV)
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (this._isSliding) {
|
||||
triggerTransitionEnd(this._element)
|
||||
}
|
||||
|
||||
this._clearInterval()
|
||||
}
|
||||
|
||||
cycle() {
|
||||
this._clearInterval()
|
||||
this._updateInterval()
|
||||
|
||||
this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)
|
||||
}
|
||||
|
||||
_maybeEnableCycle() {
|
||||
if (!this._config.ride) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._isSliding) {
|
||||
EventHandler.one(this._element, EVENT_SLID, () => this.cycle())
|
||||
return
|
||||
}
|
||||
|
||||
this.cycle()
|
||||
}
|
||||
|
||||
to(index) {
|
||||
const items = this._getItems()
|
||||
if (index > items.length - 1 || index < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._isSliding) {
|
||||
EventHandler.one(this._element, EVENT_SLID, () => this.to(index))
|
||||
return
|
||||
}
|
||||
|
||||
const activeIndex = this._getItemIndex(this._getActive())
|
||||
if (activeIndex === index) {
|
||||
return
|
||||
}
|
||||
|
||||
const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV
|
||||
|
||||
this._slide(order, items[index])
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._swipeHelper) {
|
||||
this._swipeHelper.dispose()
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config) {
|
||||
config.defaultInterval = config.interval
|
||||
return config
|
||||
}
|
||||
|
||||
_addEventListeners() {
|
||||
if (this._config.keyboard) {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))
|
||||
}
|
||||
|
||||
if (this._config.pause === 'hover') {
|
||||
EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())
|
||||
EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())
|
||||
}
|
||||
|
||||
if (this._config.touch && Swipe.isSupported()) {
|
||||
this._addTouchEventListeners()
|
||||
}
|
||||
}
|
||||
|
||||
_addTouchEventListeners() {
|
||||
for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {
|
||||
EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())
|
||||
}
|
||||
|
||||
const endCallBack = () => {
|
||||
if (this._config.pause !== 'hover') {
|
||||
return
|
||||
}
|
||||
|
||||
// If it's a touch-enabled device, mouseenter/leave are fired as
|
||||
// part of the mouse compatibility events on first tap - the carousel
|
||||
// would stop cycling until user tapped out of it;
|
||||
// here, we listen for touchend, explicitly pause the carousel
|
||||
// (as if it's the second time we tap on it, mouseenter compat event
|
||||
// is NOT fired) and after a timeout (to allow for mouse compatibility
|
||||
// events to fire) we explicitly restart cycling
|
||||
|
||||
this.pause()
|
||||
if (this.touchTimeout) {
|
||||
clearTimeout(this.touchTimeout)
|
||||
}
|
||||
|
||||
this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
|
||||
}
|
||||
|
||||
const swipeConfig = {
|
||||
leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),
|
||||
rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),
|
||||
endCallback: endCallBack
|
||||
}
|
||||
|
||||
this._swipeHelper = new Swipe(this._element, swipeConfig)
|
||||
}
|
||||
|
||||
_keydown(event) {
|
||||
if (/input|textarea/i.test(event.target.tagName)) {
|
||||
return
|
||||
}
|
||||
|
||||
const direction = KEY_TO_DIRECTION[event.key]
|
||||
if (direction) {
|
||||
event.preventDefault()
|
||||
this._slide(this._directionToOrder(direction))
|
||||
}
|
||||
}
|
||||
|
||||
_getItemIndex(element) {
|
||||
return this._getItems().indexOf(element)
|
||||
}
|
||||
|
||||
_setActiveIndicatorElement(index) {
|
||||
if (!this._indicatorsElement) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)
|
||||
|
||||
activeIndicator.classList.remove(CLASS_NAME_ACTIVE)
|
||||
activeIndicator.removeAttribute('aria-current')
|
||||
|
||||
const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement)
|
||||
|
||||
if (newActiveIndicator) {
|
||||
newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)
|
||||
newActiveIndicator.setAttribute('aria-current', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
_updateInterval() {
|
||||
const element = this._activeElement || this._getActive()
|
||||
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)
|
||||
|
||||
this._config.interval = elementInterval || this._config.defaultInterval
|
||||
}
|
||||
|
||||
_slide(order, element = null) {
|
||||
if (this._isSliding) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeElement = this._getActive()
|
||||
const isNext = order === ORDER_NEXT
|
||||
const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)
|
||||
|
||||
if (nextElement === activeElement) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextElementIndex = this._getItemIndex(nextElement)
|
||||
|
||||
const triggerEvent = eventName => {
|
||||
return EventHandler.trigger(this._element, eventName, {
|
||||
relatedTarget: nextElement,
|
||||
direction: this._orderToDirection(order),
|
||||
from: this._getItemIndex(activeElement),
|
||||
to: nextElementIndex
|
||||
})
|
||||
}
|
||||
|
||||
const slideEvent = triggerEvent(EVENT_SLIDE)
|
||||
|
||||
if (slideEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!activeElement || !nextElement) {
|
||||
// Some weirdness is happening, so we bail
|
||||
// TODO: change tests that use empty divs to avoid this check
|
||||
return
|
||||
}
|
||||
|
||||
const isCycling = Boolean(this._interval)
|
||||
this.pause()
|
||||
|
||||
this._isSliding = true
|
||||
|
||||
this._setActiveIndicatorElement(nextElementIndex)
|
||||
this._activeElement = nextElement
|
||||
|
||||
const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END
|
||||
const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV
|
||||
|
||||
nextElement.classList.add(orderClassName)
|
||||
|
||||
reflow(nextElement)
|
||||
|
||||
activeElement.classList.add(directionalClassName)
|
||||
nextElement.classList.add(directionalClassName)
|
||||
|
||||
const completeCallBack = () => {
|
||||
nextElement.classList.remove(directionalClassName, orderClassName)
|
||||
nextElement.classList.add(CLASS_NAME_ACTIVE)
|
||||
|
||||
activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)
|
||||
|
||||
this._isSliding = false
|
||||
|
||||
triggerEvent(EVENT_SLID)
|
||||
}
|
||||
|
||||
this._queueCallback(completeCallBack, activeElement, this._isAnimated())
|
||||
|
||||
if (isCycling) {
|
||||
this.cycle()
|
||||
}
|
||||
}
|
||||
|
||||
_isAnimated() {
|
||||
return this._element.classList.contains(CLASS_NAME_SLIDE)
|
||||
}
|
||||
|
||||
_getActive() {
|
||||
return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
|
||||
}
|
||||
|
||||
_getItems() {
|
||||
return SelectorEngine.find(SELECTOR_ITEM, this._element)
|
||||
}
|
||||
|
||||
_clearInterval() {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval)
|
||||
this._interval = null
|
||||
}
|
||||
}
|
||||
|
||||
_directionToOrder(direction) {
|
||||
if (isRTL()) {
|
||||
return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT
|
||||
}
|
||||
|
||||
return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV
|
||||
}
|
||||
|
||||
_orderToDirection(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 () {
|
||||
const data = Carousel.getOrCreateInstance(this, config)
|
||||
|
||||
if (typeof config === 'number') {
|
||||
data.to(config)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof config === 'string') {
|
||||
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config]()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
|
||||
const target = SelectorEngine.getElementFromSelector(this)
|
||||
|
||||
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const carousel = Carousel.getOrCreateInstance(target)
|
||||
const slideIndex = this.getAttribute('data-bs-slide-to')
|
||||
|
||||
if (slideIndex) {
|
||||
carousel.to(slideIndex)
|
||||
carousel._maybeEnableCycle()
|
||||
return
|
||||
}
|
||||
|
||||
if (Manipulator.getDataAttribute(this, 'slide') === 'next') {
|
||||
carousel.next()
|
||||
carousel._maybeEnableCycle()
|
||||
return
|
||||
}
|
||||
|
||||
carousel.prev()
|
||||
carousel._maybeEnableCycle()
|
||||
})
|
||||
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)
|
||||
|
||||
for (const carousel of carousels) {
|
||||
Carousel.getOrCreateInstance(carousel)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Carousel)
|
||||
|
||||
export default Carousel
|
297
bootstrap/js/src/collapse.js
Normal file
297
bootstrap/js/src/collapse.js
Normal file
|
@ -0,0 +1,297 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap collapse.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
getElement,
|
||||
reflow
|
||||
} from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'collapse'
|
||||
const DATA_KEY = 'bs.collapse'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
|
||||
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 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 WIDTH = 'width'
|
||||
const HEIGHT = 'height'
|
||||
|
||||
const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="collapse"]'
|
||||
|
||||
const Default = {
|
||||
parent: null,
|
||||
toggle: true
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
parent: '(null|element)',
|
||||
toggle: 'boolean'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Collapse extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._isTransitioning = false
|
||||
this._triggerArray = []
|
||||
|
||||
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
|
||||
|
||||
for (const elem of toggleList) {
|
||||
const selector = SelectorEngine.getSelectorFromElement(elem)
|
||||
const filterElement = SelectorEngine.find(selector)
|
||||
.filter(foundElement => foundElement === this._element)
|
||||
|
||||
if (selector !== null && filterElement.length) {
|
||||
this._triggerArray.push(elem)
|
||||
}
|
||||
}
|
||||
|
||||
this._initializeChildren()
|
||||
|
||||
if (!this._config.parent) {
|
||||
this._addAriaAndCollapsedClass(this._triggerArray, this._isShown())
|
||||
}
|
||||
|
||||
if (this._config.toggle) {
|
||||
this.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
if (this._isShown()) {
|
||||
this.hide()
|
||||
} else {
|
||||
this.show()
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
if (this._isTransitioning || this._isShown()) {
|
||||
return
|
||||
}
|
||||
|
||||
let activeChildren = []
|
||||
|
||||
// find active children
|
||||
if (this._config.parent) {
|
||||
activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES)
|
||||
.filter(element => element !== this._element)
|
||||
.map(element => Collapse.getOrCreateInstance(element, { toggle: false }))
|
||||
}
|
||||
|
||||
if (activeChildren.length && activeChildren[0]._isTransitioning) {
|
||||
return
|
||||
}
|
||||
|
||||
const startEvent = EventHandler.trigger(this._element, EVENT_SHOW)
|
||||
if (startEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const activeInstance of activeChildren) {
|
||||
activeInstance.hide()
|
||||
}
|
||||
|
||||
const dimension = this._getDimension()
|
||||
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSE)
|
||||
this._element.classList.add(CLASS_NAME_COLLAPSING)
|
||||
|
||||
this._element.style[dimension] = 0
|
||||
|
||||
this._addAriaAndCollapsedClass(this._triggerArray, true)
|
||||
this._isTransitioning = true
|
||||
|
||||
const complete = () => {
|
||||
this._isTransitioning = false
|
||||
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSING)
|
||||
this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)
|
||||
|
||||
this._element.style[dimension] = ''
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN)
|
||||
}
|
||||
|
||||
const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
|
||||
const scrollSize = `scroll${capitalizedDimension}`
|
||||
|
||||
this._queueCallback(complete, this._element, true)
|
||||
this._element.style[dimension] = `${this._element[scrollSize]}px`
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this._isTransitioning || !this._isShown()) {
|
||||
return
|
||||
}
|
||||
|
||||
const startEvent = EventHandler.trigger(this._element, EVENT_HIDE)
|
||||
if (startEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
const dimension = this._getDimension()
|
||||
|
||||
this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`
|
||||
|
||||
reflow(this._element)
|
||||
|
||||
this._element.classList.add(CLASS_NAME_COLLAPSING)
|
||||
this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW)
|
||||
|
||||
for (const trigger of this._triggerArray) {
|
||||
const element = SelectorEngine.getElementFromSelector(trigger)
|
||||
|
||||
if (element && !this._isShown(element)) {
|
||||
this._addAriaAndCollapsedClass([trigger], false)
|
||||
}
|
||||
}
|
||||
|
||||
this._isTransitioning = true
|
||||
|
||||
const complete = () => {
|
||||
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] = ''
|
||||
|
||||
this._queueCallback(complete, this._element, true)
|
||||
}
|
||||
|
||||
_isShown(element = this._element) {
|
||||
return element.classList.contains(CLASS_NAME_SHOW)
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config) {
|
||||
config.toggle = Boolean(config.toggle) // Coerce string values
|
||||
config.parent = getElement(config.parent)
|
||||
return config
|
||||
}
|
||||
|
||||
_getDimension() {
|
||||
return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT
|
||||
}
|
||||
|
||||
_initializeChildren() {
|
||||
if (!this._config.parent) {
|
||||
return
|
||||
}
|
||||
|
||||
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE)
|
||||
|
||||
for (const element of children) {
|
||||
const selected = SelectorEngine.getElementFromSelector(element)
|
||||
|
||||
if (selected) {
|
||||
this._addAriaAndCollapsedClass([element], this._isShown(selected))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getFirstLevelChildren(selector) {
|
||||
const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent)
|
||||
// remove children if greater depth
|
||||
return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element))
|
||||
}
|
||||
|
||||
_addAriaAndCollapsedClass(triggerArray, isOpen) {
|
||||
if (!triggerArray.length) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const element of triggerArray) {
|
||||
element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen)
|
||||
element.setAttribute('aria-expanded', isOpen)
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
const _config = {}
|
||||
if (typeof config === 'string' && /show|hide/.test(config)) {
|
||||
_config.toggle = false
|
||||
}
|
||||
|
||||
return this.each(function () {
|
||||
const data = Collapse.getOrCreateInstance(this, _config)
|
||||
|
||||
if (typeof config === 'string') {
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config]()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
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.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {
|
||||
Collapse.getOrCreateInstance(element, { toggle: false }).toggle()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Collapse)
|
||||
|
||||
export default Collapse
|
55
bootstrap/js/src/dom/data.js
Normal file
55
bootstrap/js/src/dom/data.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap 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)
|
||||
}
|
||||
}
|
||||
}
|
317
bootstrap/js/src/dom/event-handler.js
Normal file
317
bootstrap/js/src/dom/event-handler.js
Normal file
|
@ -0,0 +1,317 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/event-handler.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { getjQuery } from '../util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const namespaceRegex = /[^.]*(?=\..*)\.|.*/
|
||||
const stripNameRegex = /\..*/
|
||||
const stripUidRegex = /::\d+$/
|
||||
const eventRegistry = {} // Events storage
|
||||
let uidEvent = 1
|
||||
const customEvents = {
|
||||
mouseenter: 'mouseover',
|
||||
mouseleave: 'mouseout'
|
||||
}
|
||||
|
||||
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 makeEventUid(element, uid) {
|
||||
return (uid && `${uid}::${uidEvent++}`) || element.uidEvent || uidEvent++
|
||||
}
|
||||
|
||||
function getElementEvents(element) {
|
||||
const uid = makeEventUid(element)
|
||||
|
||||
element.uidEvent = uid
|
||||
eventRegistry[uid] = eventRegistry[uid] || {}
|
||||
|
||||
return eventRegistry[uid]
|
||||
}
|
||||
|
||||
function bootstrapHandler(element, fn) {
|
||||
return function handler(event) {
|
||||
hydrateObj(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 (const domElement of domElements) {
|
||||
if (domElement !== target) {
|
||||
continue
|
||||
}
|
||||
|
||||
hydrateObj(event, { delegateTarget: target })
|
||||
|
||||
if (handler.oneOff) {
|
||||
EventHandler.off(element, event.type, selector, fn)
|
||||
}
|
||||
|
||||
return fn.apply(target, [event])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findHandler(events, callable, delegationSelector = null) {
|
||||
return Object.values(events)
|
||||
.find(event => event.callable === callable && event.delegationSelector === delegationSelector)
|
||||
}
|
||||
|
||||
function normalizeParameters(originalTypeEvent, handler, delegationFunction) {
|
||||
const isDelegated = typeof handler === 'string'
|
||||
// TODO: tooltip passes `false` instead of selector, so we need to check
|
||||
const callable = isDelegated ? delegationFunction : (handler || delegationFunction)
|
||||
let typeEvent = getTypeEvent(originalTypeEvent)
|
||||
|
||||
if (!nativeEvents.has(typeEvent)) {
|
||||
typeEvent = originalTypeEvent
|
||||
}
|
||||
|
||||
return [isDelegated, callable, typeEvent]
|
||||
}
|
||||
|
||||
function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {
|
||||
if (typeof originalTypeEvent !== 'string' || !element) {
|
||||
return
|
||||
}
|
||||
|
||||
let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)
|
||||
|
||||
// in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position
|
||||
// this prevents the handler from being dispatched the same way as mouseover or mouseout does
|
||||
if (originalTypeEvent in customEvents) {
|
||||
const wrapFunction = fn => {
|
||||
return function (event) {
|
||||
if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) {
|
||||
return fn.call(this, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callable = wrapFunction(callable)
|
||||
}
|
||||
|
||||
const events = getElementEvents(element)
|
||||
const handlers = events[typeEvent] || (events[typeEvent] = {})
|
||||
const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null)
|
||||
|
||||
if (previousFunction) {
|
||||
previousFunction.oneOff = previousFunction.oneOff && oneOff
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''))
|
||||
const fn = isDelegated ?
|
||||
bootstrapDelegationHandler(element, handler, callable) :
|
||||
bootstrapHandler(element, callable)
|
||||
|
||||
fn.delegationSelector = isDelegated ? handler : null
|
||||
fn.callable = callable
|
||||
fn.oneOff = oneOff
|
||||
fn.uidEvent = uid
|
||||
handlers[uid] = fn
|
||||
|
||||
element.addEventListener(typeEvent, fn, isDelegated)
|
||||
}
|
||||
|
||||
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] || {}
|
||||
|
||||
for (const [handlerKey, event] of Object.entries(storeElementEvent)) {
|
||||
if (handlerKey.includes(namespace)) {
|
||||
removeHandler(element, events, typeEvent, event.callable, 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, delegationFunction) {
|
||||
addHandler(element, event, handler, delegationFunction, false)
|
||||
},
|
||||
|
||||
one(element, event, handler, delegationFunction) {
|
||||
addHandler(element, event, handler, delegationFunction, true)
|
||||
},
|
||||
|
||||
off(element, originalTypeEvent, handler, delegationFunction) {
|
||||
if (typeof originalTypeEvent !== 'string' || !element) {
|
||||
return
|
||||
}
|
||||
|
||||
const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction)
|
||||
const inNamespace = typeEvent !== originalTypeEvent
|
||||
const events = getElementEvents(element)
|
||||
const storeElementEvent = events[typeEvent] || {}
|
||||
const isNamespace = originalTypeEvent.startsWith('.')
|
||||
|
||||
if (typeof callable !== 'undefined') {
|
||||
// Simplest case: handler is passed, remove that listener ONLY.
|
||||
if (!Object.keys(storeElementEvent).length) {
|
||||
return
|
||||
}
|
||||
|
||||
removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null)
|
||||
return
|
||||
}
|
||||
|
||||
if (isNamespace) {
|
||||
for (const elementEvent of Object.keys(events)) {
|
||||
removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {
|
||||
const handlerKey = keyHandlers.replace(stripUidRegex, '')
|
||||
|
||||
if (!inNamespace || originalTypeEvent.includes(handlerKey)) {
|
||||
removeHandler(element, events, typeEvent, event.callable, event.delegationSelector)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
trigger(element, event, args) {
|
||||
if (typeof event !== 'string' || !element) {
|
||||
return null
|
||||
}
|
||||
|
||||
const $ = getjQuery()
|
||||
const typeEvent = getTypeEvent(event)
|
||||
const inNamespace = event !== typeEvent
|
||||
|
||||
let jQueryEvent = null
|
||||
let bubbles = true
|
||||
let nativeDispatch = true
|
||||
let defaultPrevented = false
|
||||
|
||||
if (inNamespace && $) {
|
||||
jQueryEvent = $.Event(event, args)
|
||||
|
||||
$(element).trigger(jQueryEvent)
|
||||
bubbles = !jQueryEvent.isPropagationStopped()
|
||||
nativeDispatch = !jQueryEvent.isImmediatePropagationStopped()
|
||||
defaultPrevented = jQueryEvent.isDefaultPrevented()
|
||||
}
|
||||
|
||||
const evt = hydrateObj(new Event(event, { bubbles, cancelable: true }), args)
|
||||
|
||||
if (defaultPrevented) {
|
||||
evt.preventDefault()
|
||||
}
|
||||
|
||||
if (nativeDispatch) {
|
||||
element.dispatchEvent(evt)
|
||||
}
|
||||
|
||||
if (evt.defaultPrevented && jQueryEvent) {
|
||||
jQueryEvent.preventDefault()
|
||||
}
|
||||
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
function hydrateObj(obj, meta = {}) {
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
try {
|
||||
obj[key] = value
|
||||
} catch {
|
||||
Object.defineProperty(obj, key, {
|
||||
configurable: true,
|
||||
get() {
|
||||
return value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
export default EventHandler
|
71
bootstrap/js/src/dom/manipulator.js
Normal file
71
bootstrap/js/src/dom/manipulator.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/manipulator.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
function normalizeData(value) {
|
||||
if (value === 'true') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (value === 'false') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (value === Number(value).toString()) {
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
if (value === '' || value === 'null') {
|
||||
return null
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(value))
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
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 = {}
|
||||
const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))
|
||||
|
||||
for (const key of bsKeys) {
|
||||
let pureKey = key.replace(/^bs/, '')
|
||||
pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length)
|
||||
attributes[pureKey] = normalizeData(element.dataset[key])
|
||||
}
|
||||
|
||||
return attributes
|
||||
},
|
||||
|
||||
getDataAttribute(element, key) {
|
||||
return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))
|
||||
}
|
||||
}
|
||||
|
||||
export default Manipulator
|
126
bootstrap/js/src/dom/selector-engine.js
Normal file
126
bootstrap/js/src/dom/selector-engine.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dom/selector-engine.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { isDisabled, isVisible, parseSelector } from '../util/index.js'
|
||||
|
||||
const getSelector = element => {
|
||||
let selector = element.getAttribute('data-bs-target')
|
||||
|
||||
if (!selector || selector === '#') {
|
||||
let hrefAttribute = element.getAttribute('href')
|
||||
|
||||
// The only valid content that could double as a selector are IDs or classes,
|
||||
// so everything starting with `#` or `.`. If a "real" URL is used as the selector,
|
||||
// `document.querySelector` will rightfully complain it is invalid.
|
||||
// See https://github.com/twbs/bootstrap/issues/32273
|
||||
if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Just in case some CMS puts out a full URL with the anchor appended
|
||||
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
||||
hrefAttribute = `#${hrefAttribute.split('#')[1]}`
|
||||
}
|
||||
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null
|
||||
}
|
||||
|
||||
return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null
|
||||
}
|
||||
|
||||
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.closest(selector)
|
||||
|
||||
while (ancestor) {
|
||||
parents.push(ancestor)
|
||||
ancestor = ancestor.parentNode.closest(selector)
|
||||
}
|
||||
|
||||
return parents
|
||||
},
|
||||
|
||||
prev(element, selector) {
|
||||
let previous = element.previousElementSibling
|
||||
|
||||
while (previous) {
|
||||
if (previous.matches(selector)) {
|
||||
return [previous]
|
||||
}
|
||||
|
||||
previous = previous.previousElementSibling
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
// TODO: this is now unused; remove later along with prev()
|
||||
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))
|
||||
},
|
||||
|
||||
getSelectorFromElement(element) {
|
||||
const selector = getSelector(element)
|
||||
|
||||
if (selector) {
|
||||
return SelectorEngine.findOne(selector) ? selector : null
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
getElementFromSelector(element) {
|
||||
const selector = getSelector(element)
|
||||
|
||||
return selector ? SelectorEngine.findOne(selector) : null
|
||||
},
|
||||
|
||||
getMultipleElementsFromSelector(element) {
|
||||
const selector = getSelector(element)
|
||||
|
||||
return selector ? SelectorEngine.find(selector) : []
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectorEngine
|
455
bootstrap/js/src/dropdown.js
Normal file
455
bootstrap/js/src/dropdown.js
Normal file
|
@ -0,0 +1,455 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap dropdown.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as Popper from '@popperjs/core'
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import Manipulator from './dom/manipulator.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
execute,
|
||||
getElement,
|
||||
getNextActiveElement,
|
||||
isDisabled,
|
||||
isElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
noop
|
||||
} from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'dropdown'
|
||||
const DATA_KEY = 'bs.dropdown'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
|
||||
const ESCAPE_KEY = 'Escape'
|
||||
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 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_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_SHOW = 'show'
|
||||
const CLASS_NAME_DROPUP = 'dropup'
|
||||
const CLASS_NAME_DROPEND = 'dropend'
|
||||
const CLASS_NAME_DROPSTART = 'dropstart'
|
||||
const CLASS_NAME_DROPUP_CENTER = 'dropup-center'
|
||||
const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'
|
||||
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)'
|
||||
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`
|
||||
const SELECTOR_MENU = '.dropdown-menu'
|
||||
const SELECTOR_NAVBAR = '.navbar'
|
||||
const SELECTOR_NAVBAR_NAV = '.navbar-nav'
|
||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
|
||||
|
||||
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 PLACEMENT_TOPCENTER = 'top'
|
||||
const PLACEMENT_BOTTOMCENTER = 'bottom'
|
||||
|
||||
const Default = {
|
||||
autoClose: true,
|
||||
boundary: 'clippingParents',
|
||||
display: 'dynamic',
|
||||
offset: [0, 2],
|
||||
popperConfig: null,
|
||||
reference: 'toggle'
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
autoClose: '(boolean|string)',
|
||||
boundary: '(string|element)',
|
||||
display: 'string',
|
||||
offset: '(array|string|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
reference: '(string|element|object)'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Dropdown extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._popper = null
|
||||
this._parent = this._element.parentNode // dropdown wrapper
|
||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] ||
|
||||
SelectorEngine.prev(this._element, SELECTOR_MENU)[0] ||
|
||||
SelectorEngine.findOne(SELECTOR_MENU, this._parent)
|
||||
this._inNavbar = this._detectNavbar()
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
return this._isShown() ? this.hide() : this.show()
|
||||
}
|
||||
|
||||
show() {
|
||||
if (isDisabled(this._element) || this._isShown()) {
|
||||
return
|
||||
}
|
||||
|
||||
const relatedTarget = {
|
||||
relatedTarget: this._element
|
||||
}
|
||||
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget)
|
||||
|
||||
if (showEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
this._createPopper()
|
||||
|
||||
// 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 && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.on(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
|
||||
this._element.focus()
|
||||
this._element.setAttribute('aria-expanded', true)
|
||||
|
||||
this._menu.classList.add(CLASS_NAME_SHOW)
|
||||
this._element.classList.add(CLASS_NAME_SHOW)
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (isDisabled(this._element) || !this._isShown()) {
|
||||
return
|
||||
}
|
||||
|
||||
const relatedTarget = {
|
||||
relatedTarget: this._element
|
||||
}
|
||||
|
||||
this._completeHide(relatedTarget)
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy()
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
update() {
|
||||
this._inNavbar = this._detectNavbar()
|
||||
if (this._popper) {
|
||||
this._popper.update()
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
_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) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.off(element, '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 = super._getConfig(config)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
_createPopper() {
|
||||
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 = this._parent
|
||||
} else if (isElement(this._config.reference)) {
|
||||
referenceElement = getElement(this._config.reference)
|
||||
} else if (typeof this._config.reference === 'object') {
|
||||
referenceElement = this._config.reference
|
||||
}
|
||||
|
||||
const popperConfig = this._getPopperConfig()
|
||||
this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig)
|
||||
}
|
||||
|
||||
_isShown() {
|
||||
return this._menu.classList.contains(CLASS_NAME_SHOW)
|
||||
}
|
||||
|
||||
_getPlacement() {
|
||||
const parentDropdown = this._parent
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
|
||||
return PLACEMENT_RIGHT
|
||||
}
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
|
||||
return PLACEMENT_LEFT
|
||||
}
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
|
||||
return PLACEMENT_TOPCENTER
|
||||
}
|
||||
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
|
||||
return PLACEMENT_BOTTOMCENTER
|
||||
}
|
||||
|
||||
// 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(SELECTOR_NAVBAR) !== null
|
||||
}
|
||||
|
||||
_getOffset() {
|
||||
const { offset } = this._config
|
||||
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10))
|
||||
}
|
||||
|
||||
if (typeof offset === 'function') {
|
||||
return popperData => offset(popperData, this._element)
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
_getPopperConfig() {
|
||||
const defaultBsPopperConfig = {
|
||||
placement: this._getPlacement(),
|
||||
modifiers: [{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: this._config.boundary
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: this._getOffset()
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// Disable Popper if we have a static display or Dropdown is in Navbar
|
||||
if (this._inNavbar || this._config.display === 'static') {
|
||||
Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove
|
||||
defaultBsPopperConfig.modifiers = [{
|
||||
name: 'applyStyles',
|
||||
enabled: false
|
||||
}]
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultBsPopperConfig,
|
||||
...execute(this._config.popperConfig, [defaultBsPopperConfig])
|
||||
}
|
||||
}
|
||||
|
||||
_selectMenuItem({ key, target }) {
|
||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element))
|
||||
|
||||
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) {
|
||||
return this.each(function () {
|
||||
const data = Dropdown.getOrCreateInstance(this, config)
|
||||
|
||||
if (typeof config !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config]()
|
||||
})
|
||||
}
|
||||
|
||||
static clearMenus(event) {
|
||||
if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) {
|
||||
return
|
||||
}
|
||||
|
||||
const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN)
|
||||
|
||||
for (const toggle of openToggles) {
|
||||
const context = Dropdown.getInstance(toggle)
|
||||
if (!context || context._config.autoClose === false) {
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
const relatedTarget = { relatedTarget: context._element }
|
||||
|
||||
if (event.type === 'click') {
|
||||
relatedTarget.clickEvent = event
|
||||
}
|
||||
|
||||
context._completeHide(relatedTarget)
|
||||
}
|
||||
}
|
||||
|
||||
static dataApiKeydownHandler(event) {
|
||||
// If not an UP | DOWN | ESCAPE key => not a dropdown command
|
||||
// If input/textarea && if key is other than ESCAPE => not a dropdown command
|
||||
|
||||
const isInput = /input|textarea/i.test(event.target.tagName)
|
||||
const isEscapeEvent = event.key === ESCAPE_KEY
|
||||
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key)
|
||||
|
||||
if (!isUpOrDownEvent && !isEscapeEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isInput && !isEscapeEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ?
|
||||
this :
|
||||
(SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] ||
|
||||
SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] ||
|
||||
SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode))
|
||||
|
||||
const instance = Dropdown.getOrCreateInstance(getToggleButton)
|
||||
|
||||
if (isUpOrDownEvent) {
|
||||
event.stopPropagation()
|
||||
instance.show()
|
||||
instance._selectMenuItem(event)
|
||||
return
|
||||
}
|
||||
|
||||
if (instance._isShown()) { // else is escape and we check if it is shown
|
||||
event.stopPropagation()
|
||||
instance.hide()
|
||||
getToggleButton.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Dropdown)
|
||||
|
||||
export default Dropdown
|
378
bootstrap/js/src/modal.js
Normal file
378
bootstrap/js/src/modal.js
Normal file
|
@ -0,0 +1,378 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap modal.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import Backdrop from './util/backdrop.js'
|
||||
import { enableDismissTrigger } from './util/component-functions.js'
|
||||
import FocusTrap from './util/focustrap.js'
|
||||
import {
|
||||
defineJQueryPlugin, isRTL, isVisible, reflow
|
||||
} from './util/index.js'
|
||||
import ScrollBarHelper from './util/scrollbar.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'modal'
|
||||
const DATA_KEY = 'bs.modal'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
const ESCAPE_KEY = 'Escape'
|
||||
|
||||
const EVENT_HIDE = `hide${EVENT_KEY}`
|
||||
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_RESIZE = `resize${EVENT_KEY}`
|
||||
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
|
||||
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`
|
||||
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
|
||||
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
|
||||
|
||||
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-bs-toggle="modal"]'
|
||||
|
||||
const Default = {
|
||||
backdrop: true,
|
||||
focus: true,
|
||||
keyboard: true
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
backdrop: '(boolean|string)',
|
||||
focus: 'boolean',
|
||||
keyboard: 'boolean'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Modal extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)
|
||||
this._backdrop = this._initializeBackDrop()
|
||||
this._focustrap = this._initializeFocusTrap()
|
||||
this._isShown = false
|
||||
this._isTransitioning = false
|
||||
this._scrollBar = new ScrollBarHelper()
|
||||
|
||||
this._addEventListeners()
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle(relatedTarget) {
|
||||
return this._isShown ? this.hide() : this.show(relatedTarget)
|
||||
}
|
||||
|
||||
show(relatedTarget) {
|
||||
if (this._isShown || this._isTransitioning) {
|
||||
return
|
||||
}
|
||||
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
|
||||
relatedTarget
|
||||
})
|
||||
|
||||
if (showEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
this._isShown = true
|
||||
this._isTransitioning = true
|
||||
|
||||
this._scrollBar.hide()
|
||||
|
||||
document.body.classList.add(CLASS_NAME_OPEN)
|
||||
|
||||
this._adjustDialog()
|
||||
|
||||
this._backdrop.show(() => this._showElement(relatedTarget))
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this._isShown || this._isTransitioning) {
|
||||
return
|
||||
}
|
||||
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)
|
||||
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
this._isShown = false
|
||||
this._isTransitioning = true
|
||||
this._focustrap.deactivate()
|
||||
|
||||
this._element.classList.remove(CLASS_NAME_SHOW)
|
||||
|
||||
this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())
|
||||
}
|
||||
|
||||
dispose() {
|
||||
EventHandler.off(window, EVENT_KEY)
|
||||
EventHandler.off(this._dialog, EVENT_KEY)
|
||||
|
||||
this._backdrop.dispose()
|
||||
this._focustrap.deactivate()
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
handleUpdate() {
|
||||
this._adjustDialog()
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
_showElement(relatedTarget) {
|
||||
// try to append dynamic modal
|
||||
if (!document.body.contains(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
|
||||
|
||||
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
|
||||
if (modalBody) {
|
||||
modalBody.scrollTop = 0
|
||||
}
|
||||
|
||||
reflow(this._element)
|
||||
|
||||
this._element.classList.add(CLASS_NAME_SHOW)
|
||||
|
||||
const transitionComplete = () => {
|
||||
if (this._config.focus) {
|
||||
this._focustrap.activate()
|
||||
}
|
||||
|
||||
this._isTransitioning = false
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, {
|
||||
relatedTarget
|
||||
})
|
||||
}
|
||||
|
||||
this._queueCallback(transitionComplete, this._dialog, this._isAnimated())
|
||||
}
|
||||
|
||||
_addEventListeners() {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
|
||||
if (event.key !== ESCAPE_KEY) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._config.keyboard) {
|
||||
this.hide()
|
||||
return
|
||||
}
|
||||
|
||||
this._triggerBackdropTransition()
|
||||
})
|
||||
|
||||
EventHandler.on(window, EVENT_RESIZE, () => {
|
||||
if (this._isShown && !this._isTransitioning) {
|
||||
this._adjustDialog()
|
||||
}
|
||||
})
|
||||
|
||||
EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {
|
||||
// a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks
|
||||
EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {
|
||||
if (this._element !== event.target || this._element !== event2.target) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._config.backdrop === 'static') {
|
||||
this._triggerBackdropTransition()
|
||||
return
|
||||
}
|
||||
|
||||
if (this._config.backdrop) {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_hideModal() {
|
||||
this._element.style.display = 'none'
|
||||
this._element.setAttribute('aria-hidden', true)
|
||||
this._element.removeAttribute('aria-modal')
|
||||
this._element.removeAttribute('role')
|
||||
this._isTransitioning = false
|
||||
|
||||
this._backdrop.hide(() => {
|
||||
document.body.classList.remove(CLASS_NAME_OPEN)
|
||||
this._resetAdjustments()
|
||||
this._scrollBar.reset()
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN)
|
||||
})
|
||||
}
|
||||
|
||||
_isAnimated() {
|
||||
return this._element.classList.contains(CLASS_NAME_FADE)
|
||||
}
|
||||
|
||||
_triggerBackdropTransition() {
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
|
||||
const initialOverflowY = this._element.style.overflowY
|
||||
// return if the following background transition hasn't yet completed
|
||||
if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isModalOverflowing) {
|
||||
this._element.style.overflowY = 'hidden'
|
||||
}
|
||||
|
||||
this._element.classList.add(CLASS_NAME_STATIC)
|
||||
this._queueCallback(() => {
|
||||
this._element.classList.remove(CLASS_NAME_STATIC)
|
||||
this._queueCallback(() => {
|
||||
this._element.style.overflowY = initialOverflowY
|
||||
}, this._dialog)
|
||||
}, this._dialog)
|
||||
|
||||
this._element.focus()
|
||||
}
|
||||
|
||||
/**
|
||||
* The following methods are used to handle overflowing modals
|
||||
*/
|
||||
|
||||
_adjustDialog() {
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
|
||||
const scrollbarWidth = this._scrollBar.getWidth()
|
||||
const isBodyOverflowing = scrollbarWidth > 0
|
||||
|
||||
if (isBodyOverflowing && !isModalOverflowing) {
|
||||
const property = isRTL() ? 'paddingLeft' : 'paddingRight'
|
||||
this._element.style[property] = `${scrollbarWidth}px`
|
||||
}
|
||||
|
||||
if (!isBodyOverflowing && isModalOverflowing) {
|
||||
const property = isRTL() ? 'paddingRight' : 'paddingLeft'
|
||||
this._element.style[property] = `${scrollbarWidth}px`
|
||||
}
|
||||
}
|
||||
|
||||
_resetAdjustments() {
|
||||
this._element.style.paddingLeft = ''
|
||||
this._element.style.paddingRight = ''
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config, relatedTarget) {
|
||||
return this.each(function () {
|
||||
const data = Modal.getOrCreateInstance(this, config)
|
||||
|
||||
if (typeof config !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config](relatedTarget)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
const target = SelectorEngine.getElementFromSelector(this)
|
||||
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
EventHandler.one(target, EVENT_SHOW, showEvent => {
|
||||
if (showEvent.defaultPrevented) {
|
||||
// only register focus restorer if modal will actually get shown
|
||||
return
|
||||
}
|
||||
|
||||
EventHandler.one(target, EVENT_HIDDEN, () => {
|
||||
if (isVisible(this)) {
|
||||
this.focus()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// avoid conflict when clicking modal toggler while another one is open
|
||||
const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)
|
||||
if (alreadyOpen) {
|
||||
Modal.getInstance(alreadyOpen).hide()
|
||||
}
|
||||
|
||||
const data = Modal.getOrCreateInstance(target)
|
||||
|
||||
data.toggle(this)
|
||||
})
|
||||
|
||||
enableDismissTrigger(Modal)
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Modal)
|
||||
|
||||
export default Modal
|
282
bootstrap/js/src/offcanvas.js
Normal file
282
bootstrap/js/src/offcanvas.js
Normal file
|
@ -0,0 +1,282 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap offcanvas.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import Backdrop from './util/backdrop.js'
|
||||
import { enableDismissTrigger } from './util/component-functions.js'
|
||||
import FocusTrap from './util/focustrap.js'
|
||||
import {
|
||||
defineJQueryPlugin,
|
||||
isDisabled,
|
||||
isVisible
|
||||
} from './util/index.js'
|
||||
import ScrollBarHelper from './util/scrollbar.js'
|
||||
|
||||
/**
|
||||
* 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 CLASS_NAME_SHOW = 'show'
|
||||
const CLASS_NAME_SHOWING = 'showing'
|
||||
const CLASS_NAME_HIDING = 'hiding'
|
||||
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_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`
|
||||
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
|
||||
const EVENT_RESIZE = `resize${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"]'
|
||||
|
||||
const Default = {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
scroll: false
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
backdrop: '(boolean|string)',
|
||||
keyboard: 'boolean',
|
||||
scroll: 'boolean'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Offcanvas extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._isShown = false
|
||||
this._backdrop = this._initializeBackDrop()
|
||||
this._focustrap = this._initializeFocusTrap()
|
||||
this._addEventListeners()
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// 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._backdrop.show()
|
||||
|
||||
if (!this._config.scroll) {
|
||||
new ScrollBarHelper().hide()
|
||||
}
|
||||
|
||||
this._element.setAttribute('aria-modal', true)
|
||||
this._element.setAttribute('role', 'dialog')
|
||||
this._element.classList.add(CLASS_NAME_SHOWING)
|
||||
|
||||
const completeCallBack = () => {
|
||||
if (!this._config.scroll || this._config.backdrop) {
|
||||
this._focustrap.activate()
|
||||
}
|
||||
|
||||
this._element.classList.add(CLASS_NAME_SHOW)
|
||||
this._element.classList.remove(CLASS_NAME_SHOWING)
|
||||
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.add(CLASS_NAME_HIDING)
|
||||
this._backdrop.hide()
|
||||
|
||||
const completeCallback = () => {
|
||||
this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING)
|
||||
this._element.removeAttribute('aria-modal')
|
||||
this._element.removeAttribute('role')
|
||||
|
||||
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
|
||||
_initializeBackDrop() {
|
||||
const clickCallback = () => {
|
||||
if (this._config.backdrop === 'static') {
|
||||
EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
|
||||
return
|
||||
}
|
||||
|
||||
this.hide()
|
||||
}
|
||||
|
||||
// 'static' option will be translated to true, and booleans will keep their value
|
||||
const isVisible = Boolean(this._config.backdrop)
|
||||
|
||||
return new Backdrop({
|
||||
className: CLASS_NAME_BACKDROP,
|
||||
isVisible,
|
||||
isAnimated: true,
|
||||
rootElement: this._element.parentNode,
|
||||
clickCallback: isVisible ? clickCallback : null
|
||||
})
|
||||
}
|
||||
|
||||
_initializeFocusTrap() {
|
||||
return new FocusTrap({
|
||||
trapElement: this._element
|
||||
})
|
||||
}
|
||||
|
||||
_addEventListeners() {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
|
||||
if (event.key !== ESCAPE_KEY) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._config.keyboard) {
|
||||
this.hide()
|
||||
return
|
||||
}
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
|
||||
})
|
||||
}
|
||||
|
||||
// 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 = SelectorEngine.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 alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)
|
||||
if (alreadyOpen && alreadyOpen !== target) {
|
||||
Offcanvas.getInstance(alreadyOpen).hide()
|
||||
}
|
||||
|
||||
const data = Offcanvas.getOrCreateInstance(target)
|
||||
data.toggle(this)
|
||||
})
|
||||
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {
|
||||
Offcanvas.getOrCreateInstance(selector).show()
|
||||
}
|
||||
})
|
||||
|
||||
EventHandler.on(window, EVENT_RESIZE, () => {
|
||||
for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {
|
||||
if (getComputedStyle(element).position !== 'fixed') {
|
||||
Offcanvas.getOrCreateInstance(element).hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
enableDismissTrigger(Offcanvas)
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Offcanvas)
|
||||
|
||||
export default Offcanvas
|
97
bootstrap/js/src/popover.js
Normal file
97
bootstrap/js/src/popover.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap popover.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import Tooltip from './tooltip.js'
|
||||
import { defineJQueryPlugin } from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'popover'
|
||||
|
||||
const SELECTOR_TITLE = '.popover-header'
|
||||
const SELECTOR_CONTENT = '.popover-body'
|
||||
|
||||
const Default = {
|
||||
...Tooltip.Default,
|
||||
content: '',
|
||||
offset: [0, 8],
|
||||
placement: 'right',
|
||||
template: '<div class="popover" role="tooltip">' +
|
||||
'<div class="popover-arrow"></div>' +
|
||||
'<h3 class="popover-header"></h3>' +
|
||||
'<div class="popover-body"></div>' +
|
||||
'</div>',
|
||||
trigger: 'click'
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
...Tooltip.DefaultType,
|
||||
content: '(null|string|element|function)'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Popover extends Tooltip {
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Overrides
|
||||
_isWithContent() {
|
||||
return this._getTitle() || this._getContent()
|
||||
}
|
||||
|
||||
// Private
|
||||
_getContentForTemplate() {
|
||||
return {
|
||||
[SELECTOR_TITLE]: this._getTitle(),
|
||||
[SELECTOR_CONTENT]: this._getContent()
|
||||
}
|
||||
}
|
||||
|
||||
_getContent() {
|
||||
return this._resolvePossibleFunction(this._config.content)
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Popover.getOrCreateInstance(this, config)
|
||||
|
||||
if (typeof config !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config]()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Popover)
|
||||
|
||||
export default Popover
|
296
bootstrap/js/src/scrollspy.js
Normal file
296
bootstrap/js/src/scrollspy.js
Normal file
|
@ -0,0 +1,296 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap scrollspy.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import {
|
||||
defineJQueryPlugin, getElement, isDisabled, isVisible
|
||||
} from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'scrollspy'
|
||||
const DATA_KEY = 'bs.scrollspy'
|
||||
const EVENT_KEY = `.${DATA_KEY}`
|
||||
const DATA_API_KEY = '.data-api'
|
||||
|
||||
const EVENT_ACTIVATE = `activate${EVENT_KEY}`
|
||||
const EVENT_CLICK = `click${EVENT_KEY}`
|
||||
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-bs-spy="scroll"]'
|
||||
const SELECTOR_TARGET_LINKS = '[href]'
|
||||
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_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`
|
||||
const SELECTOR_DROPDOWN = '.dropdown'
|
||||
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'
|
||||
|
||||
const Default = {
|
||||
offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons
|
||||
rootMargin: '0px 0px -25%',
|
||||
smoothScroll: false,
|
||||
target: null,
|
||||
threshold: [0.1, 0.5, 1]
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons
|
||||
rootMargin: 'string',
|
||||
smoothScroll: 'boolean',
|
||||
target: 'element',
|
||||
threshold: 'array'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class ScrollSpy extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
// this._element is the observablesContainer and config.target the menu links wrapper
|
||||
this._targetLinks = new Map()
|
||||
this._observableSections = new Map()
|
||||
this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element
|
||||
this._activeTarget = null
|
||||
this._observer = null
|
||||
this._previousScrollData = {
|
||||
visibleEntryTop: 0,
|
||||
parentScrollTop: 0
|
||||
}
|
||||
this.refresh() // initialize
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
refresh() {
|
||||
this._initializeTargetsAndObservables()
|
||||
this._maybeEnableSmoothScroll()
|
||||
|
||||
if (this._observer) {
|
||||
this._observer.disconnect()
|
||||
} else {
|
||||
this._observer = this._getNewObserver()
|
||||
}
|
||||
|
||||
for (const section of this._observableSections.values()) {
|
||||
this._observer.observe(section)
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._observer.disconnect()
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
// Private
|
||||
_configAfterMerge(config) {
|
||||
// TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case
|
||||
config.target = getElement(config.target) || document.body
|
||||
|
||||
// TODO: v6 Only for backwards compatibility reasons. Use rootMargin only
|
||||
config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin
|
||||
|
||||
if (typeof config.threshold === 'string') {
|
||||
config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value))
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
_maybeEnableSmoothScroll() {
|
||||
if (!this._config.smoothScroll) {
|
||||
return
|
||||
}
|
||||
|
||||
// unregister any previous listeners
|
||||
EventHandler.off(this._config.target, EVENT_CLICK)
|
||||
|
||||
EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {
|
||||
const observableSection = this._observableSections.get(event.target.hash)
|
||||
if (observableSection) {
|
||||
event.preventDefault()
|
||||
const root = this._rootElement || window
|
||||
const height = observableSection.offsetTop - this._element.offsetTop
|
||||
if (root.scrollTo) {
|
||||
root.scrollTo({ top: height, behavior: 'smooth' })
|
||||
return
|
||||
}
|
||||
|
||||
// Chrome 60 doesn't support `scrollTo`
|
||||
root.scrollTop = height
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_getNewObserver() {
|
||||
const options = {
|
||||
root: this._rootElement,
|
||||
threshold: this._config.threshold,
|
||||
rootMargin: this._config.rootMargin
|
||||
}
|
||||
|
||||
return new IntersectionObserver(entries => this._observerCallback(entries), options)
|
||||
}
|
||||
|
||||
// The logic of selection
|
||||
_observerCallback(entries) {
|
||||
const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`)
|
||||
const activate = entry => {
|
||||
this._previousScrollData.visibleEntryTop = entry.target.offsetTop
|
||||
this._process(targetElement(entry))
|
||||
}
|
||||
|
||||
const parentScrollTop = (this._rootElement || document.documentElement).scrollTop
|
||||
const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop
|
||||
this._previousScrollData.parentScrollTop = parentScrollTop
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) {
|
||||
this._activeTarget = null
|
||||
this._clearActiveClass(targetElement(entry))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop
|
||||
// if we are scrolling down, pick the bigger offsetTop
|
||||
if (userScrollsDown && entryIsLowerThanPrevious) {
|
||||
activate(entry)
|
||||
// if parent isn't scrolled, let's keep the first visible item, breaking the iteration
|
||||
if (!parentScrollTop) {
|
||||
return
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// if we are scrolling up, pick the smallest offsetTop
|
||||
if (!userScrollsDown && !entryIsLowerThanPrevious) {
|
||||
activate(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_initializeTargetsAndObservables() {
|
||||
this._targetLinks = new Map()
|
||||
this._observableSections = new Map()
|
||||
|
||||
const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target)
|
||||
|
||||
for (const anchor of targetLinks) {
|
||||
// ensure that the anchor has an id and is not disabled
|
||||
if (!anchor.hash || isDisabled(anchor)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element)
|
||||
|
||||
// ensure that the observableSection exists & is visible
|
||||
if (isVisible(observableSection)) {
|
||||
this._targetLinks.set(decodeURI(anchor.hash), anchor)
|
||||
this._observableSections.set(anchor.hash, observableSection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_process(target) {
|
||||
if (this._activeTarget === target) {
|
||||
return
|
||||
}
|
||||
|
||||
this._clearActiveClass(this._config.target)
|
||||
this._activeTarget = target
|
||||
target.classList.add(CLASS_NAME_ACTIVE)
|
||||
this._activateParents(target)
|
||||
|
||||
EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target })
|
||||
}
|
||||
|
||||
_activateParents(target) {
|
||||
// Activate dropdown parents
|
||||
if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {
|
||||
SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN))
|
||||
.classList.add(CLASS_NAME_ACTIVE)
|
||||
return
|
||||
}
|
||||
|
||||
for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {
|
||||
// Set triggered links parents as active
|
||||
// With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
|
||||
for (const item of SelectorEngine.prev(listGroup, SELECTOR_LINK_ITEMS)) {
|
||||
item.classList.add(CLASS_NAME_ACTIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_clearActiveClass(parent) {
|
||||
parent.classList.remove(CLASS_NAME_ACTIVE)
|
||||
|
||||
const activeNodes = SelectorEngine.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent)
|
||||
for (const node of activeNodes) {
|
||||
node.classList.remove(CLASS_NAME_ACTIVE)
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = ScrollSpy.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]()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
for (const spy of SelectorEngine.find(SELECTOR_DATA_SPY)) {
|
||||
ScrollSpy.getOrCreateInstance(spy)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(ScrollSpy)
|
||||
|
||||
export default ScrollSpy
|
315
bootstrap/js/src/tab.js
Normal file
315
bootstrap/js/src/tab.js
Normal file
|
@ -0,0 +1,315 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap tab.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import SelectorEngine from './dom/selector-engine.js'
|
||||
import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'tab'
|
||||
const DATA_KEY = 'bs.tab'
|
||||
const EVENT_KEY = `.${DATA_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_DATA_API = `click${EVENT_KEY}`
|
||||
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
|
||||
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`
|
||||
|
||||
const ARROW_LEFT_KEY = 'ArrowLeft'
|
||||
const ARROW_RIGHT_KEY = 'ArrowRight'
|
||||
const ARROW_UP_KEY = 'ArrowUp'
|
||||
const ARROW_DOWN_KEY = 'ArrowDown'
|
||||
const HOME_KEY = 'Home'
|
||||
const END_KEY = 'End'
|
||||
|
||||
const CLASS_NAME_ACTIVE = 'active'
|
||||
const CLASS_NAME_FADE = 'fade'
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
const CLASS_DROPDOWN = 'dropdown'
|
||||
|
||||
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'
|
||||
const SELECTOR_DROPDOWN_MENU = '.dropdown-menu'
|
||||
const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`
|
||||
|
||||
const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]'
|
||||
const SELECTOR_OUTER = '.nav-item, .list-group-item'
|
||||
const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // TODO: could only be `tab` in v6
|
||||
const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`
|
||||
|
||||
const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]`
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Tab extends BaseComponent {
|
||||
constructor(element) {
|
||||
super(element)
|
||||
this._parent = this._element.closest(SELECTOR_TAB_PANEL)
|
||||
|
||||
if (!this._parent) {
|
||||
return
|
||||
// TODO: should throw exception in v6
|
||||
// throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)
|
||||
}
|
||||
|
||||
// Set up initial aria attributes
|
||||
this._setInitialAttributes(this._parent, this._getChildren())
|
||||
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event))
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
show() { // Shows this elem and deactivate the active sibling if exists
|
||||
const innerElem = this._element
|
||||
if (this._elemIsActive(innerElem)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Search for active tab on same parent to deactivate it
|
||||
const active = this._getActiveElem()
|
||||
|
||||
const hideEvent = active ?
|
||||
EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) :
|
||||
null
|
||||
|
||||
const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active })
|
||||
|
||||
if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) {
|
||||
return
|
||||
}
|
||||
|
||||
this._deactivate(active, innerElem)
|
||||
this._activate(innerElem, active)
|
||||
}
|
||||
|
||||
// Private
|
||||
_activate(element, relatedElem) {
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
element.classList.add(CLASS_NAME_ACTIVE)
|
||||
|
||||
this._activate(SelectorEngine.getElementFromSelector(element)) // Search and activate/show the proper section
|
||||
|
||||
const complete = () => {
|
||||
if (element.getAttribute('role') !== 'tab') {
|
||||
element.classList.add(CLASS_NAME_SHOW)
|
||||
return
|
||||
}
|
||||
|
||||
element.removeAttribute('tabindex')
|
||||
element.setAttribute('aria-selected', true)
|
||||
this._toggleDropDown(element, true)
|
||||
EventHandler.trigger(element, EVENT_SHOWN, {
|
||||
relatedTarget: relatedElem
|
||||
})
|
||||
}
|
||||
|
||||
this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))
|
||||
}
|
||||
|
||||
_deactivate(element, relatedElem) {
|
||||
if (!element) {
|
||||
return
|
||||
}
|
||||
|
||||
element.classList.remove(CLASS_NAME_ACTIVE)
|
||||
element.blur()
|
||||
|
||||
this._deactivate(SelectorEngine.getElementFromSelector(element)) // Search and deactivate the shown section too
|
||||
|
||||
const complete = () => {
|
||||
if (element.getAttribute('role') !== 'tab') {
|
||||
element.classList.remove(CLASS_NAME_SHOW)
|
||||
return
|
||||
}
|
||||
|
||||
element.setAttribute('aria-selected', false)
|
||||
element.setAttribute('tabindex', '-1')
|
||||
this._toggleDropDown(element, false)
|
||||
EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem })
|
||||
}
|
||||
|
||||
this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE))
|
||||
}
|
||||
|
||||
_keydown(event) {
|
||||
if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key))) {
|
||||
return
|
||||
}
|
||||
|
||||
event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page
|
||||
event.preventDefault()
|
||||
|
||||
const children = this._getChildren().filter(element => !isDisabled(element))
|
||||
let nextActiveElement
|
||||
|
||||
if ([HOME_KEY, END_KEY].includes(event.key)) {
|
||||
nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]
|
||||
} else {
|
||||
const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)
|
||||
nextActiveElement = getNextActiveElement(children, event.target, isNext, true)
|
||||
}
|
||||
|
||||
if (nextActiveElement) {
|
||||
nextActiveElement.focus({ preventScroll: true })
|
||||
Tab.getOrCreateInstance(nextActiveElement).show()
|
||||
}
|
||||
}
|
||||
|
||||
_getChildren() { // collection of inner elements
|
||||
return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent)
|
||||
}
|
||||
|
||||
_getActiveElem() {
|
||||
return this._getChildren().find(child => this._elemIsActive(child)) || null
|
||||
}
|
||||
|
||||
_setInitialAttributes(parent, children) {
|
||||
this._setAttributeIfNotExists(parent, 'role', 'tablist')
|
||||
|
||||
for (const child of children) {
|
||||
this._setInitialAttributesOnChild(child)
|
||||
}
|
||||
}
|
||||
|
||||
_setInitialAttributesOnChild(child) {
|
||||
child = this._getInnerElement(child)
|
||||
const isActive = this._elemIsActive(child)
|
||||
const outerElem = this._getOuterElement(child)
|
||||
child.setAttribute('aria-selected', isActive)
|
||||
|
||||
if (outerElem !== child) {
|
||||
this._setAttributeIfNotExists(outerElem, 'role', 'presentation')
|
||||
}
|
||||
|
||||
if (!isActive) {
|
||||
child.setAttribute('tabindex', '-1')
|
||||
}
|
||||
|
||||
this._setAttributeIfNotExists(child, 'role', 'tab')
|
||||
|
||||
// set attributes to the related panel too
|
||||
this._setInitialAttributesOnTargetPanel(child)
|
||||
}
|
||||
|
||||
_setInitialAttributesOnTargetPanel(child) {
|
||||
const target = SelectorEngine.getElementFromSelector(child)
|
||||
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
this._setAttributeIfNotExists(target, 'role', 'tabpanel')
|
||||
|
||||
if (child.id) {
|
||||
this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
_toggleDropDown(element, open) {
|
||||
const outerElem = this._getOuterElement(element)
|
||||
if (!outerElem.classList.contains(CLASS_DROPDOWN)) {
|
||||
return
|
||||
}
|
||||
|
||||
const toggle = (selector, className) => {
|
||||
const element = SelectorEngine.findOne(selector, outerElem)
|
||||
if (element) {
|
||||
element.classList.toggle(className, open)
|
||||
}
|
||||
}
|
||||
|
||||
toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE)
|
||||
toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW)
|
||||
outerElem.setAttribute('aria-expanded', open)
|
||||
}
|
||||
|
||||
_setAttributeIfNotExists(element, attribute, value) {
|
||||
if (!element.hasAttribute(attribute)) {
|
||||
element.setAttribute(attribute, value)
|
||||
}
|
||||
}
|
||||
|
||||
_elemIsActive(elem) {
|
||||
return elem.classList.contains(CLASS_NAME_ACTIVE)
|
||||
}
|
||||
|
||||
// Try to get the inner element (usually the .nav-link)
|
||||
_getInnerElement(elem) {
|
||||
return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem)
|
||||
}
|
||||
|
||||
// Try to get the outer element (usually the .nav-item)
|
||||
_getOuterElement(elem) {
|
||||
return elem.closest(SELECTOR_OUTER) || elem
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Tab.getOrCreateInstance(this)
|
||||
|
||||
if (typeof config !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config]()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
if (isDisabled(this)) {
|
||||
return
|
||||
}
|
||||
|
||||
Tab.getOrCreateInstance(this).show()
|
||||
})
|
||||
|
||||
/**
|
||||
* Initialize on focus
|
||||
*/
|
||||
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||
for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {
|
||||
Tab.getOrCreateInstance(element)
|
||||
}
|
||||
})
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Tab)
|
||||
|
||||
export default Tab
|
225
bootstrap/js/src/toast.js
Normal file
225
bootstrap/js/src/toast.js
Normal file
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap toast.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import { enableDismissTrigger } from './util/component-functions.js'
|
||||
import { defineJQueryPlugin, reflow } from './util/index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'toast'
|
||||
const DATA_KEY = 'bs.toast'
|
||||
const EVENT_KEY = `.${DATA_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' // @deprecated - kept here only for backwards compatibility
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
const CLASS_NAME_SHOWING = 'showing'
|
||||
|
||||
const DefaultType = {
|
||||
animation: 'boolean',
|
||||
autohide: 'boolean',
|
||||
delay: 'number'
|
||||
}
|
||||
|
||||
const Default = {
|
||||
animation: true,
|
||||
autohide: true,
|
||||
delay: 5000
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Toast extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config)
|
||||
|
||||
this._timeout = null
|
||||
this._hasMouseInteraction = false
|
||||
this._hasKeyboardInteraction = false
|
||||
this._setListeners()
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
show() {
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)
|
||||
|
||||
if (showEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
this._clearTimeout()
|
||||
|
||||
if (this._config.animation) {
|
||||
this._element.classList.add(CLASS_NAME_FADE)
|
||||
}
|
||||
|
||||
const complete = () => {
|
||||
this._element.classList.remove(CLASS_NAME_SHOWING)
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN)
|
||||
|
||||
this._maybeScheduleHide()
|
||||
}
|
||||
|
||||
this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated
|
||||
reflow(this._element)
|
||||
this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)
|
||||
|
||||
this._queueCallback(complete, this._element, this._config.animation)
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.isShown()) {
|
||||
return
|
||||
}
|
||||
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)
|
||||
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
const complete = () => {
|
||||
this._element.classList.add(CLASS_NAME_HIDE) // @deprecated
|
||||
this._element.classList.remove(CLASS_NAME_SHOWING, 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() {
|
||||
this._clearTimeout()
|
||||
|
||||
if (this.isShown()) {
|
||||
this._element.classList.remove(CLASS_NAME_SHOW)
|
||||
}
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
isShown() {
|
||||
return this._element.classList.contains(CLASS_NAME_SHOW)
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
_maybeScheduleHide() {
|
||||
if (!this._config.autohide) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._hasMouseInteraction || this._hasKeyboardInteraction) {
|
||||
return
|
||||
}
|
||||
|
||||
this._timeout = setTimeout(() => {
|
||||
this.hide()
|
||||
}, this._config.delay)
|
||||
}
|
||||
|
||||
_onInteraction(event, isInteracting) {
|
||||
switch (event.type) {
|
||||
case 'mouseover':
|
||||
case 'mouseout': {
|
||||
this._hasMouseInteraction = isInteracting
|
||||
break
|
||||
}
|
||||
|
||||
case 'focusin':
|
||||
case 'focusout': {
|
||||
this._hasKeyboardInteraction = isInteracting
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (isInteracting) {
|
||||
this._clearTimeout()
|
||||
return
|
||||
}
|
||||
|
||||
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() {
|
||||
clearTimeout(this._timeout)
|
||||
this._timeout = null
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Toast.getOrCreateInstance(this, config)
|
||||
|
||||
if (typeof config === 'string') {
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config](this)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
enableDismissTrigger(Toast)
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Toast)
|
||||
|
||||
export default Toast
|
633
bootstrap/js/src/tooltip.js
Normal file
633
bootstrap/js/src/tooltip.js
Normal file
|
@ -0,0 +1,633 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap tooltip.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as Popper from '@popperjs/core'
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import Manipulator from './dom/manipulator.js'
|
||||
import {
|
||||
defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop
|
||||
} from './util/index.js'
|
||||
import { DefaultAllowlist } from './util/sanitizer.js'
|
||||
import TemplateFactory from './util/template-factory.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'tooltip'
|
||||
const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn'])
|
||||
|
||||
const CLASS_NAME_FADE = 'fade'
|
||||
const CLASS_NAME_MODAL = 'modal'
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
|
||||
const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'
|
||||
const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`
|
||||
|
||||
const EVENT_MODAL_HIDE = 'hide.bs.modal'
|
||||
|
||||
const TRIGGER_HOVER = 'hover'
|
||||
const TRIGGER_FOCUS = 'focus'
|
||||
const TRIGGER_CLICK = 'click'
|
||||
const TRIGGER_MANUAL = 'manual'
|
||||
|
||||
const EVENT_HIDE = 'hide'
|
||||
const EVENT_HIDDEN = 'hidden'
|
||||
const EVENT_SHOW = 'show'
|
||||
const EVENT_SHOWN = 'shown'
|
||||
const EVENT_INSERTED = 'inserted'
|
||||
const EVENT_CLICK = 'click'
|
||||
const EVENT_FOCUSIN = 'focusin'
|
||||
const EVENT_FOCUSOUT = 'focusout'
|
||||
const EVENT_MOUSEENTER = 'mouseenter'
|
||||
const EVENT_MOUSELEAVE = 'mouseleave'
|
||||
|
||||
const AttachmentMap = {
|
||||
AUTO: 'auto',
|
||||
TOP: 'top',
|
||||
RIGHT: isRTL() ? 'left' : 'right',
|
||||
BOTTOM: 'bottom',
|
||||
LEFT: isRTL() ? 'right' : 'left'
|
||||
}
|
||||
|
||||
const Default = {
|
||||
allowList: DefaultAllowlist,
|
||||
animation: true,
|
||||
boundary: 'clippingParents',
|
||||
container: false,
|
||||
customClass: '',
|
||||
delay: 0,
|
||||
fallbackPlacements: ['top', 'right', 'bottom', 'left'],
|
||||
html: false,
|
||||
offset: [0, 6],
|
||||
placement: 'top',
|
||||
popperConfig: null,
|
||||
sanitize: true,
|
||||
sanitizeFn: null,
|
||||
selector: false,
|
||||
template: '<div class="tooltip" role="tooltip">' +
|
||||
'<div class="tooltip-arrow"></div>' +
|
||||
'<div class="tooltip-inner"></div>' +
|
||||
'</div>',
|
||||
title: '',
|
||||
trigger: 'hover focus'
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
allowList: 'object',
|
||||
animation: 'boolean',
|
||||
boundary: '(string|element)',
|
||||
container: '(string|element|boolean)',
|
||||
customClass: '(string|function)',
|
||||
delay: '(number|object)',
|
||||
fallbackPlacements: 'array',
|
||||
html: 'boolean',
|
||||
offset: '(array|string|function)',
|
||||
placement: '(string|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
sanitize: 'boolean',
|
||||
sanitizeFn: '(null|function)',
|
||||
selector: '(string|boolean)',
|
||||
template: 'string',
|
||||
title: '(string|element|function)',
|
||||
trigger: 'string'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
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, config)
|
||||
|
||||
// Private
|
||||
this._isEnabled = true
|
||||
this._timeout = 0
|
||||
this._isHovered = null
|
||||
this._activeTrigger = {}
|
||||
this._popper = null
|
||||
this._templateFactory = null
|
||||
this._newContent = null
|
||||
|
||||
// Protected
|
||||
this.tip = null
|
||||
|
||||
this._setListeners()
|
||||
|
||||
if (!this._config.selector) {
|
||||
this._fixTitle()
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
enable() {
|
||||
this._isEnabled = true
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._isEnabled = false
|
||||
}
|
||||
|
||||
toggleEnabled() {
|
||||
this._isEnabled = !this._isEnabled
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (!this._isEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this._activeTrigger.click = !this._activeTrigger.click
|
||||
if (this._isShown()) {
|
||||
this._leave()
|
||||
return
|
||||
}
|
||||
|
||||
this._enter()
|
||||
}
|
||||
|
||||
dispose() {
|
||||
clearTimeout(this._timeout)
|
||||
|
||||
EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)
|
||||
|
||||
if (this._element.getAttribute('data-bs-original-title')) {
|
||||
this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))
|
||||
}
|
||||
|
||||
this._disposePopper()
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
show() {
|
||||
if (this._element.style.display === 'none') {
|
||||
throw new Error('Please use show on visible elements')
|
||||
}
|
||||
|
||||
if (!(this._isWithContent() && this._isEnabled)) {
|
||||
return
|
||||
}
|
||||
|
||||
const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW))
|
||||
const shadowRoot = findShadowRoot(this._element)
|
||||
const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element)
|
||||
|
||||
if (showEvent.defaultPrevented || !isInTheDom) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: v6 remove this or make it optional
|
||||
this._disposePopper()
|
||||
|
||||
const tip = this._getTipElement()
|
||||
|
||||
this._element.setAttribute('aria-describedby', tip.getAttribute('id'))
|
||||
|
||||
const { container } = this._config
|
||||
|
||||
if (!this._element.ownerDocument.documentElement.contains(this.tip)) {
|
||||
container.append(tip)
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))
|
||||
}
|
||||
|
||||
this._popper = this._createPopper(tip)
|
||||
|
||||
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) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.on(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
|
||||
const complete = () => {
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN))
|
||||
|
||||
if (this._isHovered === false) {
|
||||
this._leave()
|
||||
}
|
||||
|
||||
this._isHovered = false
|
||||
}
|
||||
|
||||
this._queueCallback(complete, this.tip, this._isAnimated())
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this._isShown()) {
|
||||
return
|
||||
}
|
||||
|
||||
const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE))
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return
|
||||
}
|
||||
|
||||
const tip = this._getTipElement()
|
||||
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) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.off(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
|
||||
this._activeTrigger[TRIGGER_CLICK] = false
|
||||
this._activeTrigger[TRIGGER_FOCUS] = false
|
||||
this._activeTrigger[TRIGGER_HOVER] = false
|
||||
this._isHovered = null // it is a trick to support manual triggering
|
||||
|
||||
const complete = () => {
|
||||
if (this._isWithActiveTrigger()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._isHovered) {
|
||||
this._disposePopper()
|
||||
}
|
||||
|
||||
this._element.removeAttribute('aria-describedby')
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))
|
||||
}
|
||||
|
||||
this._queueCallback(complete, this.tip, this._isAnimated())
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this._popper) {
|
||||
this._popper.update()
|
||||
}
|
||||
}
|
||||
|
||||
// Protected
|
||||
_isWithContent() {
|
||||
return Boolean(this._getTitle())
|
||||
}
|
||||
|
||||
_getTipElement() {
|
||||
if (!this.tip) {
|
||||
this.tip = this._createTipElement(this._newContent || this._getContentForTemplate())
|
||||
}
|
||||
|
||||
return this.tip
|
||||
}
|
||||
|
||||
_createTipElement(content) {
|
||||
const tip = this._getTemplateFactory(content).toHtml()
|
||||
|
||||
// TODO: remove this check in v6
|
||||
if (!tip) {
|
||||
return null
|
||||
}
|
||||
|
||||
tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW)
|
||||
// TODO: v6 the following can be achieved with CSS only
|
||||
tip.classList.add(`bs-${this.constructor.NAME}-auto`)
|
||||
|
||||
const tipId = getUID(this.constructor.NAME).toString()
|
||||
|
||||
tip.setAttribute('id', tipId)
|
||||
|
||||
if (this._isAnimated()) {
|
||||
tip.classList.add(CLASS_NAME_FADE)
|
||||
}
|
||||
|
||||
return tip
|
||||
}
|
||||
|
||||
setContent(content) {
|
||||
this._newContent = content
|
||||
if (this._isShown()) {
|
||||
this._disposePopper()
|
||||
this.show()
|
||||
}
|
||||
}
|
||||
|
||||
_getTemplateFactory(content) {
|
||||
if (this._templateFactory) {
|
||||
this._templateFactory.changeContent(content)
|
||||
} else {
|
||||
this._templateFactory = new TemplateFactory({
|
||||
...this._config,
|
||||
// the `content` var has to be after `this._config`
|
||||
// to override config.content in case of popover
|
||||
content,
|
||||
extraClass: this._resolvePossibleFunction(this._config.customClass)
|
||||
})
|
||||
}
|
||||
|
||||
return this._templateFactory
|
||||
}
|
||||
|
||||
_getContentForTemplate() {
|
||||
return {
|
||||
[SELECTOR_TOOLTIP_INNER]: this._getTitle()
|
||||
}
|
||||
}
|
||||
|
||||
_getTitle() {
|
||||
return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title')
|
||||
}
|
||||
|
||||
// Private
|
||||
_initializeOnDelegatedTarget(event) {
|
||||
return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig())
|
||||
}
|
||||
|
||||
_isAnimated() {
|
||||
return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE))
|
||||
}
|
||||
|
||||
_isShown() {
|
||||
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW)
|
||||
}
|
||||
|
||||
_createPopper(tip) {
|
||||
const placement = execute(this._config.placement, [this, tip, this._element])
|
||||
const attachment = AttachmentMap[placement.toUpperCase()]
|
||||
return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
|
||||
}
|
||||
|
||||
_getOffset() {
|
||||
const { offset } = this._config
|
||||
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10))
|
||||
}
|
||||
|
||||
if (typeof offset === 'function') {
|
||||
return popperData => offset(popperData, this._element)
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
_resolvePossibleFunction(arg) {
|
||||
return execute(arg, [this._element])
|
||||
}
|
||||
|
||||
_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: 'preSetPlacement',
|
||||
enabled: true,
|
||||
phase: 'beforeMain',
|
||||
fn: data => {
|
||||
// Pre-set Popper's placement attribute in order to read the arrow sizes properly.
|
||||
// Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
|
||||
this._getTipElement().setAttribute('data-popper-placement', data.state.placement)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultBsPopperConfig,
|
||||
...execute(this._config.popperConfig, [defaultBsPopperConfig])
|
||||
}
|
||||
}
|
||||
|
||||
_setListeners() {
|
||||
const triggers = this._config.trigger.split(' ')
|
||||
|
||||
for (const trigger of triggers) {
|
||||
if (trigger === 'click') {
|
||||
EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => {
|
||||
const context = this._initializeOnDelegatedTarget(event)
|
||||
context.toggle()
|
||||
})
|
||||
} else if (trigger !== TRIGGER_MANUAL) {
|
||||
const eventIn = trigger === TRIGGER_HOVER ?
|
||||
this.constructor.eventName(EVENT_MOUSEENTER) :
|
||||
this.constructor.eventName(EVENT_FOCUSIN)
|
||||
const eventOut = trigger === TRIGGER_HOVER ?
|
||||
this.constructor.eventName(EVENT_MOUSELEAVE) :
|
||||
this.constructor.eventName(EVENT_FOCUSOUT)
|
||||
|
||||
EventHandler.on(this._element, eventIn, this._config.selector, event => {
|
||||
const context = this._initializeOnDelegatedTarget(event)
|
||||
context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true
|
||||
context._enter()
|
||||
})
|
||||
EventHandler.on(this._element, eventOut, this._config.selector, event => {
|
||||
const context = this._initializeOnDelegatedTarget(event)
|
||||
context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] =
|
||||
context._element.contains(event.relatedTarget)
|
||||
|
||||
context._leave()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this._hideModalHandler = () => {
|
||||
if (this._element) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)
|
||||
}
|
||||
|
||||
_fixTitle() {
|
||||
const title = this._element.getAttribute('title')
|
||||
|
||||
if (!title) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {
|
||||
this._element.setAttribute('aria-label', title)
|
||||
}
|
||||
|
||||
this._element.setAttribute('data-bs-original-title', title) // DO NOT USE IT. Is only for backwards compatibility
|
||||
this._element.removeAttribute('title')
|
||||
}
|
||||
|
||||
_enter() {
|
||||
if (this._isShown() || this._isHovered) {
|
||||
this._isHovered = true
|
||||
return
|
||||
}
|
||||
|
||||
this._isHovered = true
|
||||
|
||||
this._setTimeout(() => {
|
||||
if (this._isHovered) {
|
||||
this.show()
|
||||
}
|
||||
}, this._config.delay.show)
|
||||
}
|
||||
|
||||
_leave() {
|
||||
if (this._isWithActiveTrigger()) {
|
||||
return
|
||||
}
|
||||
|
||||
this._isHovered = false
|
||||
|
||||
this._setTimeout(() => {
|
||||
if (!this._isHovered) {
|
||||
this.hide()
|
||||
}
|
||||
}, this._config.delay.hide)
|
||||
}
|
||||
|
||||
_setTimeout(handler, timeout) {
|
||||
clearTimeout(this._timeout)
|
||||
this._timeout = setTimeout(handler, timeout)
|
||||
}
|
||||
|
||||
_isWithActiveTrigger() {
|
||||
return Object.values(this._activeTrigger).includes(true)
|
||||
}
|
||||
|
||||
_getConfig(config) {
|
||||
const dataAttributes = Manipulator.getDataAttributes(this._element)
|
||||
|
||||
for (const dataAttribute of Object.keys(dataAttributes)) {
|
||||
if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {
|
||||
delete dataAttributes[dataAttribute]
|
||||
}
|
||||
}
|
||||
|
||||
config = {
|
||||
...dataAttributes,
|
||||
...(typeof config === 'object' && config ? config : {})
|
||||
}
|
||||
config = this._mergeConfigObj(config)
|
||||
config = this._configAfterMerge(config)
|
||||
this._typeCheckConfig(config)
|
||||
return config
|
||||
}
|
||||
|
||||
_configAfterMerge(config) {
|
||||
config.container = config.container === false ? document.body : getElement(config.container)
|
||||
|
||||
if (typeof config.delay === 'number') {
|
||||
config.delay = {
|
||||
show: config.delay,
|
||||
hide: config.delay
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof config.title === 'number') {
|
||||
config.title = config.title.toString()
|
||||
}
|
||||
|
||||
if (typeof config.content === 'number') {
|
||||
config.content = config.content.toString()
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
_getDelegateConfig() {
|
||||
const config = {}
|
||||
|
||||
for (const [key, value] of Object.entries(this._config)) {
|
||||
if (this.constructor.Default[key] !== value) {
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
config.selector = false
|
||||
config.trigger = 'manual'
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
_disposePopper() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy()
|
||||
this._popper = null
|
||||
}
|
||||
|
||||
if (this.tip) {
|
||||
this.tip.remove()
|
||||
this.tip = null
|
||||
}
|
||||
}
|
||||
|
||||
// Static
|
||||
static jQueryInterface(config) {
|
||||
return this.each(function () {
|
||||
const data = Tooltip.getOrCreateInstance(this, config)
|
||||
|
||||
if (typeof config !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof data[config] === 'undefined') {
|
||||
throw new TypeError(`No method named "${config}"`)
|
||||
}
|
||||
|
||||
data[config]()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
|
||||
defineJQueryPlugin(Tooltip)
|
||||
|
||||
export default Tooltip
|
151
bootstrap/js/src/util/backdrop.js
Normal file
151
bootstrap/js/src/util/backdrop.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/backdrop.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import EventHandler from '../dom/event-handler.js'
|
||||
import Config from './config.js'
|
||||
import {
|
||||
execute, executeAfterTransition, getElement, reflow
|
||||
} from './index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'backdrop'
|
||||
const CLASS_NAME_FADE = 'fade'
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
const EVENT_MOUSEDOWN = `mousedown.bs.${NAME}`
|
||||
|
||||
const Default = {
|
||||
className: 'modal-backdrop',
|
||||
clickCallback: null,
|
||||
isAnimated: false,
|
||||
isVisible: true, // if false, we use the backdrop helper without adding any element to the dom
|
||||
rootElement: 'body' // give the choice to place backdrop under different elements
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
className: 'string',
|
||||
clickCallback: '(function|null)',
|
||||
isAnimated: 'boolean',
|
||||
isVisible: 'boolean',
|
||||
rootElement: '(element|string)'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Backdrop extends Config {
|
||||
constructor(config) {
|
||||
super()
|
||||
this._config = this._getConfig(config)
|
||||
this._isAppended = false
|
||||
this._element = null
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
show(callback) {
|
||||
if (!this._config.isVisible) {
|
||||
execute(callback)
|
||||
return
|
||||
}
|
||||
|
||||
this._append()
|
||||
|
||||
const element = this._getElement()
|
||||
if (this._config.isAnimated) {
|
||||
reflow(element)
|
||||
}
|
||||
|
||||
element.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)
|
||||
})
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (!this._isAppended) {
|
||||
return
|
||||
}
|
||||
|
||||
EventHandler.off(this._element, EVENT_MOUSEDOWN)
|
||||
|
||||
this._element.remove()
|
||||
this._isAppended = false
|
||||
}
|
||||
|
||||
// Private
|
||||
_getElement() {
|
||||
if (!this._element) {
|
||||
const backdrop = document.createElement('div')
|
||||
backdrop.className = this._config.className
|
||||
if (this._config.isAnimated) {
|
||||
backdrop.classList.add(CLASS_NAME_FADE)
|
||||
}
|
||||
|
||||
this._element = backdrop
|
||||
}
|
||||
|
||||
return this._element
|
||||
}
|
||||
|
||||
_configAfterMerge(config) {
|
||||
// use getElement() with the default "body" to get a fresh Element on each instantiation
|
||||
config.rootElement = getElement(config.rootElement)
|
||||
return config
|
||||
}
|
||||
|
||||
_append() {
|
||||
if (this._isAppended) {
|
||||
return
|
||||
}
|
||||
|
||||
const element = this._getElement()
|
||||
this._config.rootElement.append(element)
|
||||
|
||||
EventHandler.on(element, EVENT_MOUSEDOWN, () => {
|
||||
execute(this._config.clickCallback)
|
||||
})
|
||||
|
||||
this._isAppended = true
|
||||
}
|
||||
|
||||
_emulateAnimation(callback) {
|
||||
executeAfterTransition(callback, this._getElement(), this._config.isAnimated)
|
||||
}
|
||||
}
|
||||
|
||||
export default Backdrop
|
35
bootstrap/js/src/util/component-functions.js
Normal file
35
bootstrap/js/src/util/component-functions.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/component-functions.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import EventHandler from '../dom/event-handler.js'
|
||||
import SelectorEngine from '../dom/selector-engine.js'
|
||||
import { isDisabled } from './index.js'
|
||||
|
||||
const enableDismissTrigger = (component, method = 'hide') => {
|
||||
const clickEvent = `click.dismiss${component.EVENT_KEY}`
|
||||
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 = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)
|
||||
const instance = component.getOrCreateInstance(target)
|
||||
|
||||
// Method argument is left, for Alert and only, as it doesn't implement the 'hide' method
|
||||
instance[method]()
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
enableDismissTrigger
|
||||
}
|
65
bootstrap/js/src/util/config.js
Normal file
65
bootstrap/js/src/util/config.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/config.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import Manipulator from '../dom/manipulator.js'
|
||||
import { isElement, toType } from './index.js'
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Config {
|
||||
// Getters
|
||||
static get Default() {
|
||||
return {}
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return {}
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
throw new Error('You have to implement the static method "NAME", for each component!')
|
||||
}
|
||||
|
||||
_getConfig(config) {
|
||||
config = this._mergeConfigObj(config)
|
||||
config = this._configAfterMerge(config)
|
||||
this._typeCheckConfig(config)
|
||||
return config
|
||||
}
|
||||
|
||||
_configAfterMerge(config) {
|
||||
return config
|
||||
}
|
||||
|
||||
_mergeConfigObj(config, element) {
|
||||
const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse
|
||||
|
||||
return {
|
||||
...this.constructor.Default,
|
||||
...(typeof jsonConfig === 'object' ? jsonConfig : {}),
|
||||
...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),
|
||||
...(typeof config === 'object' ? config : {})
|
||||
}
|
||||
}
|
||||
|
||||
_typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
|
||||
for (const [property, expectedTypes] of Object.entries(configTypes)) {
|
||||
const value = config[property]
|
||||
const valueType = isElement(value) ? 'element' : toType(value)
|
||||
|
||||
if (!new RegExp(expectedTypes).test(valueType)) {
|
||||
throw new TypeError(
|
||||
`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Config
|
115
bootstrap/js/src/util/focustrap.js
Normal file
115
bootstrap/js/src/util/focustrap.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/focustrap.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import EventHandler from '../dom/event-handler.js'
|
||||
import SelectorEngine from '../dom/selector-engine.js'
|
||||
import Config from './config.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
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'
|
||||
|
||||
const Default = {
|
||||
autofocus: true,
|
||||
trapElement: null // The element to trap focus inside of
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
autofocus: 'boolean',
|
||||
trapElement: 'element'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class FocusTrap extends Config {
|
||||
constructor(config) {
|
||||
super()
|
||||
this._config = this._getConfig(config)
|
||||
this._isActive = false
|
||||
this._lastTabNavDirection = null
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
activate() {
|
||||
if (this._isActive) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._config.autofocus) {
|
||||
this._config.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 { trapElement } = this._config
|
||||
|
||||
if (event.target === document || event.target === trapElement || trapElement.contains(event.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
|
||||
}
|
||||
}
|
||||
|
||||
export default FocusTrap
|
306
bootstrap/js/src/util/index.js
Normal file
306
bootstrap/js/src/util/index.js
Normal file
|
@ -0,0 +1,306 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/index.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
const MAX_UID = 1_000_000
|
||||
const MILLISECONDS_MULTIPLIER = 1000
|
||||
const TRANSITION_END = 'transitionend'
|
||||
|
||||
/**
|
||||
* Properly escape IDs selectors to handle weird IDs
|
||||
* @param {string} selector
|
||||
* @returns {string}
|
||||
*/
|
||||
const parseSelector = selector => {
|
||||
if (selector && window.CSS && window.CSS.escape) {
|
||||
// document.querySelector needs escaping to handle IDs (html5+) containing for instance /
|
||||
selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
|
||||
}
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
// Shout-out Angus Croll (https://goo.gl/pxwQGp)
|
||||
const toType = object => {
|
||||
if (object === null || object === undefined) {
|
||||
return `${object}`
|
||||
}
|
||||
|
||||
return Object.prototype.toString.call(object).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 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 = object => {
|
||||
if (!object || typeof object !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof object.jquery !== 'undefined') {
|
||||
object = object[0]
|
||||
}
|
||||
|
||||
return typeof object.nodeType !== 'undefined'
|
||||
}
|
||||
|
||||
const getElement = object => {
|
||||
// it's a jQuery object or a node element
|
||||
if (isElement(object)) {
|
||||
return object.jquery ? object[0] : object
|
||||
}
|
||||
|
||||
if (typeof object === 'string' && object.length > 0) {
|
||||
return document.querySelector(parseSelector(object))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const isVisible = element => {
|
||||
if (!isElement(element) || element.getClientRects().length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'
|
||||
// Handle `details` element as its content may falsie appear visible when it is closed
|
||||
const closedDetails = element.closest('details:not([open])')
|
||||
|
||||
if (!closedDetails) {
|
||||
return elementIsVisible
|
||||
}
|
||||
|
||||
if (closedDetails !== element) {
|
||||
const summary = element.closest('summary')
|
||||
if (summary && summary.parentNode !== closedDetails) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (summary === null) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return elementIsVisible
|
||||
}
|
||||
|
||||
const isDisabled = element => {
|
||||
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 => {
|
||||
element.offsetHeight // eslint-disable-line no-unused-expressions
|
||||
}
|
||||
|
||||
const getjQuery = () => {
|
||||
if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
|
||||
return window.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', () => {
|
||||
for (const callback of DOMContentLoadedCallbacks) {
|
||||
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 = (possibleCallback, args = [], defaultValue = possibleCallback) => {
|
||||
return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue
|
||||
}
|
||||
|
||||
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) => {
|
||||
const listLength = list.length
|
||||
let index = list.indexOf(activeElement)
|
||||
|
||||
// if the element does not exist in the list return an element
|
||||
// depending on the direction and if cycle is allowed
|
||||
if (index === -1) {
|
||||
return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]
|
||||
}
|
||||
|
||||
index += shouldGetNext ? 1 : -1
|
||||
|
||||
if (isCycleAllowed) {
|
||||
index = (index + listLength) % listLength
|
||||
}
|
||||
|
||||
return list[Math.max(0, Math.min(index, listLength - 1))]
|
||||
}
|
||||
|
||||
export {
|
||||
defineJQueryPlugin,
|
||||
execute,
|
||||
executeAfterTransition,
|
||||
findShadowRoot,
|
||||
getElement,
|
||||
getjQuery,
|
||||
getNextActiveElement,
|
||||
getTransitionDurationFromElement,
|
||||
getUID,
|
||||
isDisabled,
|
||||
isElement,
|
||||
isRTL,
|
||||
isVisible,
|
||||
noop,
|
||||
onDOMContentLoaded,
|
||||
parseSelector,
|
||||
reflow,
|
||||
triggerTransitionEnd,
|
||||
toType
|
||||
}
|
117
bootstrap/js/src/util/sanitizer.js
Normal file
117
bootstrap/js/src/util/sanitizer.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/sanitizer.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// js-docs-start allow-list
|
||||
const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
|
||||
|
||||
export const DefaultAllowlist = {
|
||||
// Global attributes allowed on any supplied element below.
|
||||
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
|
||||
a: ['target', 'href', 'title', 'rel'],
|
||||
area: [],
|
||||
b: [],
|
||||
br: [],
|
||||
col: [],
|
||||
code: [],
|
||||
dd: [],
|
||||
div: [],
|
||||
dl: [],
|
||||
dt: [],
|
||||
em: [],
|
||||
hr: [],
|
||||
h1: [],
|
||||
h2: [],
|
||||
h3: [],
|
||||
h4: [],
|
||||
h5: [],
|
||||
h6: [],
|
||||
i: [],
|
||||
img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
|
||||
li: [],
|
||||
ol: [],
|
||||
p: [],
|
||||
pre: [],
|
||||
s: [],
|
||||
small: [],
|
||||
span: [],
|
||||
sub: [],
|
||||
sup: [],
|
||||
strong: [],
|
||||
u: [],
|
||||
ul: []
|
||||
}
|
||||
// js-docs-end allow-list
|
||||
|
||||
const uriAttributes = new Set([
|
||||
'background',
|
||||
'cite',
|
||||
'href',
|
||||
'itemtype',
|
||||
'longdesc',
|
||||
'poster',
|
||||
'src',
|
||||
'xlink:href'
|
||||
])
|
||||
|
||||
/**
|
||||
* A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
|
||||
* contexts.
|
||||
*
|
||||
* Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
|
||||
*/
|
||||
// eslint-disable-next-line unicorn/better-regex
|
||||
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
|
||||
|
||||
const allowedAttribute = (attribute, allowedAttributeList) => {
|
||||
const attributeName = attribute.nodeName.toLowerCase()
|
||||
|
||||
if (allowedAttributeList.includes(attributeName)) {
|
||||
if (uriAttributes.has(attributeName)) {
|
||||
return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if a regular expression validates the attribute.
|
||||
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
|
||||
.some(regex => regex.test(attributeName))
|
||||
}
|
||||
|
||||
export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
|
||||
if (!unsafeHtml.length) {
|
||||
return unsafeHtml
|
||||
}
|
||||
|
||||
if (sanitizeFunction && typeof sanitizeFunction === 'function') {
|
||||
return sanitizeFunction(unsafeHtml)
|
||||
}
|
||||
|
||||
const domParser = new window.DOMParser()
|
||||
const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
|
||||
const elements = [].concat(...createdDocument.body.querySelectorAll('*'))
|
||||
|
||||
for (const element of elements) {
|
||||
const elementName = element.nodeName.toLowerCase()
|
||||
|
||||
if (!Object.keys(allowList).includes(elementName)) {
|
||||
element.remove()
|
||||
continue
|
||||
}
|
||||
|
||||
const attributeList = [].concat(...element.attributes)
|
||||
const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])
|
||||
|
||||
for (const attribute of attributeList) {
|
||||
if (!allowedAttribute(attribute, allowedAttributes)) {
|
||||
element.removeAttribute(attribute.nodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createdDocument.body.innerHTML
|
||||
}
|
114
bootstrap/js/src/util/scrollbar.js
Normal file
114
bootstrap/js/src/util/scrollbar.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/scrollBar.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import Manipulator from '../dom/manipulator.js'
|
||||
import SelectorEngine from '../dom/selector-engine.js'
|
||||
import { isElement } from './index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
|
||||
const SELECTOR_STICKY_CONTENT = '.sticky-top'
|
||||
const PROPERTY_PADDING = 'padding-right'
|
||||
const PROPERTY_MARGIN = 'margin-right'
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class ScrollBarHelper {
|
||||
constructor() {
|
||||
this._element = document.body
|
||||
}
|
||||
|
||||
// Public
|
||||
getWidth() {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
|
||||
const documentWidth = document.documentElement.clientWidth
|
||||
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, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
|
||||
// trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
|
||||
this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
|
||||
this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._resetElementAttributes(this._element, 'overflow')
|
||||
this._resetElementAttributes(this._element, PROPERTY_PADDING)
|
||||
this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)
|
||||
this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)
|
||||
}
|
||||
|
||||
isOverflowing() {
|
||||
return this.getWidth() > 0
|
||||
}
|
||||
|
||||
// Private
|
||||
_disableOverFlow() {
|
||||
this._saveInitialAttribute(this._element, 'overflow')
|
||||
this._element.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
_setElementAttributes(selector, styleProperty, callback) {
|
||||
const scrollbarWidth = this.getWidth()
|
||||
const manipulationCallBack = element => {
|
||||
if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
|
||||
return
|
||||
}
|
||||
|
||||
this._saveInitialAttribute(element, styleProperty)
|
||||
const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)
|
||||
element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)
|
||||
}
|
||||
|
||||
this._applyManipulationCallback(selector, manipulationCallBack)
|
||||
}
|
||||
|
||||
_saveInitialAttribute(element, styleProperty) {
|
||||
const actualValue = element.style.getPropertyValue(styleProperty)
|
||||
if (actualValue) {
|
||||
Manipulator.setDataAttribute(element, styleProperty, actualValue)
|
||||
}
|
||||
}
|
||||
|
||||
_resetElementAttributes(selector, styleProperty) {
|
||||
const manipulationCallBack = element => {
|
||||
const value = Manipulator.getDataAttribute(element, styleProperty)
|
||||
// We only want to remove the property if the value is `null`; the value can also be zero
|
||||
if (value === null) {
|
||||
element.style.removeProperty(styleProperty)
|
||||
return
|
||||
}
|
||||
|
||||
Manipulator.removeDataAttribute(element, styleProperty)
|
||||
element.style.setProperty(styleProperty, value)
|
||||
}
|
||||
|
||||
this._applyManipulationCallback(selector, manipulationCallBack)
|
||||
}
|
||||
|
||||
_applyManipulationCallback(selector, callBack) {
|
||||
if (isElement(selector)) {
|
||||
callBack(selector)
|
||||
return
|
||||
}
|
||||
|
||||
for (const sel of SelectorEngine.find(selector, this._element)) {
|
||||
callBack(sel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ScrollBarHelper
|
146
bootstrap/js/src/util/swipe.js
Normal file
146
bootstrap/js/src/util/swipe.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/swipe.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import EventHandler from '../dom/event-handler.js'
|
||||
import Config from './config.js'
|
||||
import { execute } from './index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'swipe'
|
||||
const EVENT_KEY = '.bs.swipe'
|
||||
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
|
||||
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
|
||||
const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
|
||||
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
|
||||
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
|
||||
const POINTER_TYPE_TOUCH = 'touch'
|
||||
const POINTER_TYPE_PEN = 'pen'
|
||||
const CLASS_NAME_POINTER_EVENT = 'pointer-event'
|
||||
const SWIPE_THRESHOLD = 40
|
||||
|
||||
const Default = {
|
||||
endCallback: null,
|
||||
leftCallback: null,
|
||||
rightCallback: null
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
endCallback: '(function|null)',
|
||||
leftCallback: '(function|null)',
|
||||
rightCallback: '(function|null)'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Swipe extends Config {
|
||||
constructor(element, config) {
|
||||
super()
|
||||
this._element = element
|
||||
|
||||
if (!element || !Swipe.isSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
this._config = this._getConfig(config)
|
||||
this._deltaX = 0
|
||||
this._supportPointerEvents = Boolean(window.PointerEvent)
|
||||
this._initEvents()
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
dispose() {
|
||||
EventHandler.off(this._element, EVENT_KEY)
|
||||
}
|
||||
|
||||
// Private
|
||||
_start(event) {
|
||||
if (!this._supportPointerEvents) {
|
||||
this._deltaX = event.touches[0].clientX
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this._eventIsPointerPenTouch(event)) {
|
||||
this._deltaX = event.clientX
|
||||
}
|
||||
}
|
||||
|
||||
_end(event) {
|
||||
if (this._eventIsPointerPenTouch(event)) {
|
||||
this._deltaX = event.clientX - this._deltaX
|
||||
}
|
||||
|
||||
this._handleSwipe()
|
||||
execute(this._config.endCallback)
|
||||
}
|
||||
|
||||
_move(event) {
|
||||
this._deltaX = event.touches && event.touches.length > 1 ?
|
||||
0 :
|
||||
event.touches[0].clientX - this._deltaX
|
||||
}
|
||||
|
||||
_handleSwipe() {
|
||||
const absDeltaX = Math.abs(this._deltaX)
|
||||
|
||||
if (absDeltaX <= SWIPE_THRESHOLD) {
|
||||
return
|
||||
}
|
||||
|
||||
const direction = absDeltaX / this._deltaX
|
||||
|
||||
this._deltaX = 0
|
||||
|
||||
if (!direction) {
|
||||
return
|
||||
}
|
||||
|
||||
execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback)
|
||||
}
|
||||
|
||||
_initEvents() {
|
||||
if (this._supportPointerEvents) {
|
||||
EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event))
|
||||
EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event))
|
||||
|
||||
this._element.classList.add(CLASS_NAME_POINTER_EVENT)
|
||||
} else {
|
||||
EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event))
|
||||
EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event))
|
||||
EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event))
|
||||
}
|
||||
}
|
||||
|
||||
_eventIsPointerPenTouch(event) {
|
||||
return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)
|
||||
}
|
||||
|
||||
// Static
|
||||
static isSupported() {
|
||||
return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
|
||||
}
|
||||
}
|
||||
|
||||
export default Swipe
|
160
bootstrap/js/src/util/template-factory.js
Normal file
160
bootstrap/js/src/util/template-factory.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/template-factory.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import SelectorEngine from '../dom/selector-engine.js'
|
||||
import Config from './config.js'
|
||||
import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js'
|
||||
import { execute, getElement, isElement } from './index.js'
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'TemplateFactory'
|
||||
|
||||
const Default = {
|
||||
allowList: DefaultAllowlist,
|
||||
content: {}, // { selector : text , selector2 : text2 , }
|
||||
extraClass: '',
|
||||
html: false,
|
||||
sanitize: true,
|
||||
sanitizeFn: null,
|
||||
template: '<div></div>'
|
||||
}
|
||||
|
||||
const DefaultType = {
|
||||
allowList: 'object',
|
||||
content: 'object',
|
||||
extraClass: '(string|function)',
|
||||
html: 'boolean',
|
||||
sanitize: 'boolean',
|
||||
sanitizeFn: '(null|function)',
|
||||
template: 'string'
|
||||
}
|
||||
|
||||
const DefaultContentType = {
|
||||
entry: '(string|element|function|null)',
|
||||
selector: '(string|element)'
|
||||
}
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class TemplateFactory extends Config {
|
||||
constructor(config) {
|
||||
super()
|
||||
this._config = this._getConfig(config)
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default
|
||||
}
|
||||
|
||||
static get DefaultType() {
|
||||
return DefaultType
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return NAME
|
||||
}
|
||||
|
||||
// Public
|
||||
getContent() {
|
||||
return Object.values(this._config.content)
|
||||
.map(config => this._resolvePossibleFunction(config))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
hasContent() {
|
||||
return this.getContent().length > 0
|
||||
}
|
||||
|
||||
changeContent(content) {
|
||||
this._checkContent(content)
|
||||
this._config.content = { ...this._config.content, ...content }
|
||||
return this
|
||||
}
|
||||
|
||||
toHtml() {
|
||||
const templateWrapper = document.createElement('div')
|
||||
templateWrapper.innerHTML = this._maybeSanitize(this._config.template)
|
||||
|
||||
for (const [selector, text] of Object.entries(this._config.content)) {
|
||||
this._setContent(templateWrapper, text, selector)
|
||||
}
|
||||
|
||||
const template = templateWrapper.children[0]
|
||||
const extraClass = this._resolvePossibleFunction(this._config.extraClass)
|
||||
|
||||
if (extraClass) {
|
||||
template.classList.add(...extraClass.split(' '))
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
// Private
|
||||
_typeCheckConfig(config) {
|
||||
super._typeCheckConfig(config)
|
||||
this._checkContent(config.content)
|
||||
}
|
||||
|
||||
_checkContent(arg) {
|
||||
for (const [selector, content] of Object.entries(arg)) {
|
||||
super._typeCheckConfig({ selector, entry: content }, DefaultContentType)
|
||||
}
|
||||
}
|
||||
|
||||
_setContent(template, content, selector) {
|
||||
const templateElement = SelectorEngine.findOne(selector, template)
|
||||
|
||||
if (!templateElement) {
|
||||
return
|
||||
}
|
||||
|
||||
content = this._resolvePossibleFunction(content)
|
||||
|
||||
if (!content) {
|
||||
templateElement.remove()
|
||||
return
|
||||
}
|
||||
|
||||
if (isElement(content)) {
|
||||
this._putElementInTemplate(getElement(content), templateElement)
|
||||
return
|
||||
}
|
||||
|
||||
if (this._config.html) {
|
||||
templateElement.innerHTML = this._maybeSanitize(content)
|
||||
return
|
||||
}
|
||||
|
||||
templateElement.textContent = content
|
||||
}
|
||||
|
||||
_maybeSanitize(arg) {
|
||||
return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg
|
||||
}
|
||||
|
||||
_resolvePossibleFunction(arg) {
|
||||
return execute(arg, [this])
|
||||
}
|
||||
|
||||
_putElementInTemplate(element, templateElement) {
|
||||
if (this._config.html) {
|
||||
templateElement.innerHTML = ''
|
||||
templateElement.append(element)
|
||||
return
|
||||
}
|
||||
|
||||
templateElement.textContent = element.textContent
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateFactory
|
73
bootstrap/js/tests/README.md
Normal file
73
bootstrap/js/tests/README.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
## How does Bootstrap's test suite work?
|
||||
|
||||
Bootstrap uses [Jasmine](https://jasmine.github.io/). Each plugin has a file dedicated to its tests in `tests/unit/<plugin-name>.spec.js`.
|
||||
|
||||
- `visual/` contains "visual" tests which are run interactively in real browsers and require manual verification by humans.
|
||||
|
||||
To run the unit test suite via [Karma](https://karma-runner.github.io/), run `npm run js-test`.
|
||||
To run the unit test suite via [Karma](https://karma-runner.github.io/) and debug, run `npm run js-debug`.
|
||||
|
||||
## How do I add a new unit test?
|
||||
|
||||
1. Locate and open the file dedicated to the plugin which you need to add tests to (`tests/unit/<plugin-name>.spec.js`).
|
||||
2. Review the [Jasmine API Documentation](https://jasmine.github.io/pages/docs_home.html) and use the existing tests as references for how to structure your new tests.
|
||||
3. Write the necessary unit test(s) for the new or revised functionality.
|
||||
4. Run `npm run js-test` to see the results of your newly-added test(s).
|
||||
|
||||
**Note:** Your new unit tests should fail before your changes are applied to the plugin, and should pass after your changes are applied to the plugin.
|
||||
|
||||
## What should a unit test look like?
|
||||
|
||||
- Each test should have a unique name clearly stating what unit is being tested.
|
||||
- Each test should be in the corresponding `describe`.
|
||||
- Each test should test only one unit per test, although one test can include several assertions. Create multiple tests for multiple units of functionality.
|
||||
- Each test should use [`expect`](https://jasmine.github.io/api/edge/matchers.html) to ensure something is expected.
|
||||
- Each test should follow the project's [JavaScript Code Guidelines](https://github.com/twbs/bootstrap/blob/main/.github/CONTRIBUTING.md#js)
|
||||
|
||||
## Code coverage
|
||||
|
||||
Currently we're aiming for at least 90% test coverage for our code. To ensure your changes meet or exceed this limit, run `npm run js-test-karma` and open the file in `js/coverage/lcov-report/index.html` to see the code coverage for each plugin. See more details when you select a plugin and ensure your change is fully covered by unit tests.
|
||||
|
||||
### Example tests
|
||||
|
||||
```js
|
||||
// Synchronous test
|
||||
describe('getInstance', () => {
|
||||
it('should return null if there is no instance', () => {
|
||||
// Make assertion
|
||||
expect(Tab.getInstance(fixtureEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return this instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const divEl = fixtureEl.querySelector('div')
|
||||
const tab = new Tab(divEl)
|
||||
|
||||
// Make assertion
|
||||
expect(Tab.getInstance(divEl)).toEqual(tab)
|
||||
})
|
||||
})
|
||||
|
||||
// Asynchronous test
|
||||
it('should show a tooltip without the animation', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"></a>'
|
||||
|
||||
const tooltipEl = fixtureEl.querySelector('a')
|
||||
const tooltip = new Tooltip(tooltipEl, {
|
||||
animation: false
|
||||
})
|
||||
|
||||
tooltipEl.addEventListener('shown.bs.tooltip', () => {
|
||||
const tip = document.querySelector('.tooltip')
|
||||
|
||||
expect(tip).not.toBeNull()
|
||||
expect(tip.classList.contains('fade')).toEqual(false)
|
||||
resolve()
|
||||
})
|
||||
|
||||
tooltip.show()
|
||||
})
|
||||
})
|
||||
```
|
80
bootstrap/js/tests/browsers.js
Normal file
80
bootstrap/js/tests/browsers.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
'use strict'
|
||||
|
||||
const browsers = {
|
||||
safariMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Safari',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
chromeMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Chrome',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
firefoxMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Firefox',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
chromeWin10: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Chrome',
|
||||
browser_version: '60'
|
||||
},
|
||||
firefoxWin10: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
browser_version: '60'
|
||||
},
|
||||
chromeWin10Latest: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Chrome',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
firefoxWin10Latest: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
iphone7: {
|
||||
base: 'BrowserStack',
|
||||
os: 'ios',
|
||||
os_version: '12.0',
|
||||
device: 'iPhone 7',
|
||||
real_mobile: true
|
||||
},
|
||||
iphone12: {
|
||||
base: 'BrowserStack',
|
||||
os: 'ios',
|
||||
os_version: '14.0',
|
||||
device: 'iPhone 12',
|
||||
real_mobile: true
|
||||
},
|
||||
pixel2: {
|
||||
base: 'BrowserStack',
|
||||
os: 'android',
|
||||
os_version: '8.0',
|
||||
device: 'Google Pixel 2',
|
||||
real_mobile: true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
browsers
|
||||
}
|
47
bootstrap/js/tests/helpers/fixture.js
Normal file
47
bootstrap/js/tests/helpers/fixture.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
const fixtureId = 'fixture'
|
||||
|
||||
export const getFixture = () => {
|
||||
let fixtureElement = document.getElementById(fixtureId)
|
||||
|
||||
if (!fixtureElement) {
|
||||
fixtureElement = document.createElement('div')
|
||||
fixtureElement.setAttribute('id', fixtureId)
|
||||
fixtureElement.style.position = 'absolute'
|
||||
fixtureElement.style.top = '-10000px'
|
||||
fixtureElement.style.left = '-10000px'
|
||||
fixtureElement.style.width = '10000px'
|
||||
fixtureElement.style.height = '10000px'
|
||||
document.body.append(fixtureElement)
|
||||
}
|
||||
|
||||
return fixtureElement
|
||||
}
|
||||
|
||||
export const clearFixture = () => {
|
||||
const fixtureElement = getFixture()
|
||||
|
||||
fixtureElement.innerHTML = ''
|
||||
}
|
||||
|
||||
export const createEvent = (eventName, parameters = {}) => {
|
||||
return new Event(eventName, parameters)
|
||||
}
|
||||
|
||||
export const jQueryMock = {
|
||||
elements: undefined,
|
||||
fn: {},
|
||||
each(fn) {
|
||||
for (const element of this.elements) {
|
||||
fn.call(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const clearBodyAndDocument = () => {
|
||||
const attributes = ['data-bs-padding-right', 'style']
|
||||
|
||||
for (const attribute of attributes) {
|
||||
document.documentElement.removeAttribute(attribute)
|
||||
document.body.removeAttribute(attribute)
|
||||
}
|
||||
}
|
9
bootstrap/js/tests/integration/bundle-modularity.js
Normal file
9
bootstrap/js/tests/integration/bundle-modularity.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable import/extensions, import/no-unassigned-import */
|
||||
|
||||
import Tooltip from '../../dist/tooltip'
|
||||
import '../../dist/carousel'
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
[].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
.map(tooltipNode => new Tooltip(tooltipNode))
|
||||
})
|
6
bootstrap/js/tests/integration/bundle.js
Normal file
6
bootstrap/js/tests/integration/bundle.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { Tooltip } from '../../../dist/js/bootstrap.esm.js'
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
[].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
.map(tooltipNode => new Tooltip(tooltipNode))
|
||||
})
|
67
bootstrap/js/tests/integration/index.html
Normal file
67
bootstrap/js/tests/integration/index.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="../../../dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<title>Hello, world!</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-4">
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
<div class="mt-5">
|
||||
<button type="button" class="btn btn-secondary mb-3" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
|
||||
Tooltip on top
|
||||
</button>
|
||||
|
||||
<div id="carouselExampleIndicators" class="carousel slide mt-2" data-bs-ride="carousel">
|
||||
<div class="carousel-indicators">
|
||||
<button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="0" aria-label="Slide 1"></button>
|
||||
<button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="1" class="active" aria-current="true" aria-label="Slide 2"></button>
|
||||
<button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="2" aria-label="Slide 3"></button>
|
||||
</div>
|
||||
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-item">
|
||||
<img class="d-block w-100" alt="First slide" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22800%22%20height%3D%22400%22%20preserveAspectRatio%3D%22none%22%20viewBox%3D%220%200%20800%20400%22%3E%3Cpath%20fill%3D%22%23777%22%20d%3D%22M0%200h800v400H0z%22%2F%3E%3Ctext%20x%3D%22285.922%22%20y%3D%22217.7%22%20fill%3D%22%23555%22%20font-family%3D%22Helvetica%2Cmonospace%22%20font-size%3D%2240pt%22%20font-weight%3D%22400%22%3EFirst%20slide%3C%2Ftext%3E%3C%2Fsvg%3E">
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h5>First slide label</h5>
|
||||
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel-item active">
|
||||
<img class="d-block w-100" alt="Second slide" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22800%22%20height%3D%22400%22%20preserveAspectRatio%3D%22none%22%20viewBox%3D%220%200%20800%20400%22%3E%3Cpath%20fill%3D%22%23777%22%20d%3D%22M0%200h800v400H0z%22%2F%3E%3Ctext%20x%3D%22285.922%22%20y%3D%22217.7%22%20fill%3D%22%23555%22%20font-family%3D%22Helvetica%2Cmonospace%22%20font-size%3D%2240pt%22%20font-weight%3D%22400%22%3ESecond%20slide%3C%2Ftext%3E%3C%2Fsvg%3E">
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h5>Second slide label</h5>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<img class="d-block w-100" alt="Third slide" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22800%22%20height%3D%22400%22%20preserveAspectRatio%3D%22none%22%20viewBox%3D%220%200%20800%20400%22%3E%3Cpath%20fill%3D%22%23777%22%20d%3D%22M0%200h800v400H0z%22%2F%3E%3Ctext%20x%3D%22285.922%22%20y%3D%22217.7%22%20fill%3D%22%23555%22%20font-family%3D%22Helvetica%2Cmonospace%22%20font-size%3D%2240pt%22%20font-weight%3D%22400%22%3EThird%20slide%3C%2Ftext%3E%3C%2Fsvg%3E">
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h5>Third slide label</h5>
|
||||
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</a>
|
||||
<a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../coverage/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
17
bootstrap/js/tests/integration/rollup.bundle-modularity.js
Normal file
17
bootstrap/js/tests/integration/rollup.bundle-modularity.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
'use strict'
|
||||
|
||||
const commonjs = require('@rollup/plugin-commonjs')
|
||||
const configRollup = require('./rollup.bundle.js')
|
||||
|
||||
const config = {
|
||||
...configRollup,
|
||||
input: 'js/tests/integration/bundle-modularity.js',
|
||||
output: {
|
||||
file: 'js/coverage/bundle-modularity.js',
|
||||
format: 'iife'
|
||||
}
|
||||
}
|
||||
|
||||
config.plugins.unshift(commonjs())
|
||||
|
||||
module.exports = config
|
24
bootstrap/js/tests/integration/rollup.bundle.js
Normal file
24
bootstrap/js/tests/integration/rollup.bundle.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
'use strict'
|
||||
|
||||
const { babel } = require('@rollup/plugin-babel')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const replace = require('@rollup/plugin-replace')
|
||||
|
||||
module.exports = {
|
||||
input: 'js/tests/integration/bundle.js',
|
||||
output: {
|
||||
file: 'js/coverage/bundle.js',
|
||||
format: 'iife'
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
preventAssignment: true
|
||||
}),
|
||||
nodeResolve(),
|
||||
babel({
|
||||
exclude: 'node_modules/**',
|
||||
babelHelpers: 'bundled'
|
||||
})
|
||||
]
|
||||
}
|
169
bootstrap/js/tests/karma.conf.js
Normal file
169
bootstrap/js/tests/karma.conf.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('node:path')
|
||||
const ip = require('ip')
|
||||
const { babel } = require('@rollup/plugin-babel')
|
||||
const istanbul = require('rollup-plugin-istanbul')
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||
const replace = require('@rollup/plugin-replace')
|
||||
const { browsers } = require('./browsers.js')
|
||||
|
||||
const ENV = process.env
|
||||
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
|
||||
const DEBUG = Boolean(ENV.DEBUG)
|
||||
const JQUERY_TEST = Boolean(ENV.JQUERY)
|
||||
|
||||
const frameworks = [
|
||||
'jasmine'
|
||||
]
|
||||
|
||||
const plugins = [
|
||||
'karma-jasmine',
|
||||
'karma-rollup-preprocessor'
|
||||
]
|
||||
|
||||
const reporters = ['dots']
|
||||
|
||||
const detectBrowsers = {
|
||||
usePhantomJS: false,
|
||||
postDetection(availableBrowser) {
|
||||
// On CI just use Chrome
|
||||
if (ENV.CI === true) {
|
||||
return ['ChromeHeadless']
|
||||
}
|
||||
|
||||
if (availableBrowser.includes('Chrome')) {
|
||||
return DEBUG ? ['Chrome'] : ['ChromeHeadless']
|
||||
}
|
||||
|
||||
if (availableBrowser.includes('Chromium')) {
|
||||
return DEBUG ? ['Chromium'] : ['ChromiumHeadless']
|
||||
}
|
||||
|
||||
if (availableBrowser.includes('Firefox')) {
|
||||
return DEBUG ? ['Firefox'] : ['FirefoxHeadless']
|
||||
}
|
||||
|
||||
throw new Error('Please install Chrome, Chromium or Firefox')
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
basePath: '../..',
|
||||
port: 9876,
|
||||
colors: true,
|
||||
autoWatch: false,
|
||||
singleRun: true,
|
||||
concurrency: Number.POSITIVE_INFINITY,
|
||||
client: {
|
||||
clearContext: false
|
||||
},
|
||||
files: [
|
||||
'node_modules/hammer-simulator/index.js',
|
||||
{
|
||||
pattern: 'js/tests/unit/**/!(jquery).spec.js',
|
||||
watched: !BROWSERSTACK
|
||||
}
|
||||
],
|
||||
preprocessors: {
|
||||
'js/tests/unit/**/*.spec.js': ['rollup']
|
||||
},
|
||||
rollupPreprocessor: {
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.NODE_ENV': '"dev"',
|
||||
preventAssignment: true
|
||||
}),
|
||||
istanbul({
|
||||
exclude: [
|
||||
'node_modules/**',
|
||||
'js/tests/unit/**/*.spec.js',
|
||||
'js/tests/helpers/**/*.js'
|
||||
]
|
||||
}),
|
||||
babel({
|
||||
// Only transpile our source code
|
||||
exclude: 'node_modules/**',
|
||||
// Inline the required helpers in each file
|
||||
babelHelpers: 'inline'
|
||||
}),
|
||||
nodeResolve()
|
||||
],
|
||||
output: {
|
||||
format: 'iife',
|
||||
name: 'bootstrapTest',
|
||||
sourcemap: 'inline',
|
||||
generatedCode: 'es2015'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (BROWSERSTACK) {
|
||||
config.hostname = ip.address()
|
||||
config.browserStack = {
|
||||
username: ENV.BROWSER_STACK_USERNAME,
|
||||
accessKey: ENV.BROWSER_STACK_ACCESS_KEY,
|
||||
build: `bootstrap-${ENV.GITHUB_SHA ? `${ENV.GITHUB_SHA.slice(0, 7)}-` : ''}${new Date().toISOString()}`,
|
||||
project: 'Bootstrap',
|
||||
retryLimit: 2
|
||||
}
|
||||
plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter')
|
||||
config.customLaunchers = browsers
|
||||
config.browsers = Object.keys(browsers)
|
||||
reporters.push('BrowserStack', 'kjhtml')
|
||||
} else if (JQUERY_TEST) {
|
||||
frameworks.push('detectBrowsers')
|
||||
plugins.push(
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-detect-browsers'
|
||||
)
|
||||
config.detectBrowsers = detectBrowsers
|
||||
config.files = [
|
||||
'node_modules/jquery/dist/jquery.slim.min.js',
|
||||
{
|
||||
pattern: 'js/tests/unit/jquery.spec.js',
|
||||
watched: false
|
||||
}
|
||||
]
|
||||
} else {
|
||||
frameworks.push('detectBrowsers')
|
||||
plugins.push(
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-detect-browsers',
|
||||
'karma-coverage-istanbul-reporter'
|
||||
)
|
||||
reporters.push('coverage-istanbul')
|
||||
config.detectBrowsers = detectBrowsers
|
||||
config.coverageIstanbulReporter = {
|
||||
dir: path.resolve(__dirname, '../coverage/'),
|
||||
reports: ['lcov', 'text-summary'],
|
||||
thresholds: {
|
||||
emitWarning: false,
|
||||
global: {
|
||||
statements: 90,
|
||||
branches: 89,
|
||||
functions: 90,
|
||||
lines: 90
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
config.hostname = ip.address()
|
||||
plugins.push('karma-jasmine-html-reporter')
|
||||
reporters.push('kjhtml')
|
||||
config.singleRun = false
|
||||
config.autoWatch = true
|
||||
}
|
||||
}
|
||||
|
||||
config.frameworks = frameworks
|
||||
config.plugins = plugins
|
||||
config.reporters = reporters
|
||||
|
||||
module.exports = karmaConfig => {
|
||||
config.logLevel = karmaConfig.LOG_ERROR
|
||||
karmaConfig.set(config)
|
||||
}
|
259
bootstrap/js/tests/unit/alert.spec.js
Normal file
259
bootstrap/js/tests/unit/alert.spec.js
Normal file
|
@ -0,0 +1,259 @@
|
|||
import Alert from '../../src/alert.js'
|
||||
import { getTransitionDurationFromElement } from '../../src/util/index.js'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Alert', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = fixtureEl.querySelector('.alert')
|
||||
const alertBySelector = new Alert('.alert')
|
||||
const alertByElement = new Alert(alertEl)
|
||||
|
||||
expect(alertBySelector._element).toEqual(alertEl)
|
||||
expect(alertByElement._element).toEqual(alertEl)
|
||||
})
|
||||
|
||||
it('should return version', () => {
|
||||
expect(Alert.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(Alert.DATA_KEY).toEqual('bs.alert')
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-api', () => {
|
||||
it('should close an alert without instantiating it manually', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="alert">',
|
||||
' <button type="button" data-bs-dismiss="alert">x</button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const button = document.querySelector('button')
|
||||
|
||||
button.click()
|
||||
expect(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
})
|
||||
|
||||
it('should close an alert without instantiating it manually with the parent selector', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="alert">',
|
||||
' <button type="button" data-bs-target=".alert" data-bs-dismiss="alert">x</button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const button = document.querySelector('button')
|
||||
|
||||
button.click()
|
||||
expect(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('close', () => {
|
||||
it('should close an alert', () => {
|
||||
return new Promise(resolve => {
|
||||
const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = document.querySelector('.alert')
|
||||
const alert = new Alert(alertEl)
|
||||
|
||||
alertEl.addEventListener('closed.bs.alert', () => {
|
||||
expect(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
alert.close()
|
||||
})
|
||||
})
|
||||
|
||||
it('should close alert with fade class', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="alert fade"></div>'
|
||||
|
||||
const alertEl = document.querySelector('.alert')
|
||||
const alert = new Alert(alertEl)
|
||||
|
||||
alertEl.addEventListener('transitionend', () => {
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
alertEl.addEventListener('closed.bs.alert', () => {
|
||||
expect(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
resolve()
|
||||
})
|
||||
|
||||
alert.close()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not remove alert if close event is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const getAlert = () => document.querySelector('.alert')
|
||||
const alertEl = getAlert()
|
||||
const alert = new Alert(alertEl)
|
||||
|
||||
alertEl.addEventListener('close.bs.alert', event => {
|
||||
event.preventDefault()
|
||||
setTimeout(() => {
|
||||
expect(getAlert()).not.toBeNull()
|
||||
resolve()
|
||||
}, 10)
|
||||
})
|
||||
|
||||
alertEl.addEventListener('closed.bs.alert', () => {
|
||||
reject(new Error('should not fire closed event'))
|
||||
})
|
||||
|
||||
alert.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose an alert', () => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = document.querySelector('.alert')
|
||||
const alert = new Alert(alertEl)
|
||||
|
||||
expect(Alert.getInstance(alertEl)).not.toBeNull()
|
||||
|
||||
alert.dispose()
|
||||
|
||||
expect(Alert.getInstance(alertEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('jQueryInterface', () => {
|
||||
it('should handle config passed and toggle existing alert', () => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = fixtureEl.querySelector('.alert')
|
||||
const alert = new Alert(alertEl)
|
||||
|
||||
const spy = spyOn(alert, 'close')
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [alertEl]
|
||||
|
||||
jQueryMock.fn.alert.call(jQueryMock, 'close')
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create new alert instance and call close', () => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = fixtureEl.querySelector('.alert')
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [alertEl]
|
||||
|
||||
expect(Alert.getInstance(alertEl)).toBeNull()
|
||||
jQueryMock.fn.alert.call(jQueryMock, 'close')
|
||||
|
||||
expect(fixtureEl.querySelector('.alert')).toBeNull()
|
||||
})
|
||||
|
||||
it('should just create an alert instance without calling close', () => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = fixtureEl.querySelector('.alert')
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [alertEl]
|
||||
|
||||
jQueryMock.fn.alert.call(jQueryMock)
|
||||
|
||||
expect(Alert.getInstance(alertEl)).not.toBeNull()
|
||||
expect(fixtureEl.querySelector('.alert')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should throw an error on undefined method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = 'undefinedMethod'
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.alert.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should throw an error on protected method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = '_getConfig'
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.alert.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const alert = new Alert(div)
|
||||
|
||||
expect(Alert.getInstance(div)).toEqual(alert)
|
||||
expect(Alert.getInstance(div)).toBeInstanceOf(Alert)
|
||||
})
|
||||
|
||||
it('should return null when there is no alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Alert.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const alert = new Alert(div)
|
||||
|
||||
expect(Alert.getOrCreateInstance(div)).toEqual(alert)
|
||||
expect(Alert.getInstance(div)).toEqual(Alert.getOrCreateInstance(div, {}))
|
||||
expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Alert.getInstance(div)).toBeNull()
|
||||
expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)
|
||||
})
|
||||
})
|
||||
})
|
168
bootstrap/js/tests/unit/base-component.spec.js
Normal file
168
bootstrap/js/tests/unit/base-component.spec.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
import BaseComponent from '../../src/base-component.js'
|
||||
import EventHandler from '../../src/dom/event-handler.js'
|
||||
import { noop } from '../../src/util/index.js'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture.js'
|
||||
|
||||
class DummyClass extends BaseComponent {
|
||||
constructor(element) {
|
||||
super(element)
|
||||
|
||||
EventHandler.on(this._element, `click${DummyClass.EVENT_KEY}`, noop)
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return 'dummy'
|
||||
}
|
||||
}
|
||||
|
||||
describe('Base Component', () => {
|
||||
let fixtureEl
|
||||
const name = 'dummy'
|
||||
let element
|
||||
let instance
|
||||
const createInstance = () => {
|
||||
fixtureEl.innerHTML = '<div id="foo"></div>'
|
||||
element = fixtureEl.querySelector('#foo')
|
||||
instance = new DummyClass(element)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('Static Methods', () => {
|
||||
describe('VERSION', () => {
|
||||
it('should return version', () => {
|
||||
expect(DummyClass.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(DummyClass.DATA_KEY).toEqual(`bs.${name}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('NAME', () => {
|
||||
it('should throw an Error if it is not initialized', () => {
|
||||
expect(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
BaseComponent.NAME
|
||||
}).toThrowError(Error)
|
||||
})
|
||||
|
||||
it('should return plugin NAME', () => {
|
||||
expect(DummyClass.NAME).toEqual(name)
|
||||
})
|
||||
})
|
||||
|
||||
describe('EVENT_KEY', () => {
|
||||
it('should return plugin event key', () => {
|
||||
expect(DummyClass.EVENT_KEY).toEqual(`.bs.${name}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Public Methods', () => {
|
||||
describe('constructor', () => {
|
||||
it('should accept element, either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo"></div>',
|
||||
'<div id="bar"></div>'
|
||||
].join('')
|
||||
|
||||
const el = fixtureEl.querySelector('#foo')
|
||||
const elInstance = new DummyClass(el)
|
||||
const selectorInstance = new DummyClass('#bar')
|
||||
|
||||
expect(elInstance._element).toEqual(el)
|
||||
expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar'))
|
||||
})
|
||||
|
||||
it('should not initialize and add element record to Data (caching), if argument `element` is not an HTML element', () => {
|
||||
fixtureEl.innerHTML = ''
|
||||
|
||||
const el = fixtureEl.querySelector('#foo')
|
||||
const elInstance = new DummyClass(el)
|
||||
const selectorInstance = new DummyClass('#bar')
|
||||
|
||||
expect(elInstance._element).not.toBeDefined()
|
||||
expect(selectorInstance._element).not.toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose an component', () => {
|
||||
createInstance()
|
||||
expect(DummyClass.getInstance(element)).not.toBeNull()
|
||||
|
||||
instance.dispose()
|
||||
|
||||
expect(DummyClass.getInstance(element)).toBeNull()
|
||||
expect(instance._element).toBeNull()
|
||||
})
|
||||
|
||||
it('should de-register element event listeners', () => {
|
||||
createInstance()
|
||||
const spy = spyOn(EventHandler, 'off')
|
||||
|
||||
instance.dispose()
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return an instance', () => {
|
||||
createInstance()
|
||||
|
||||
expect(DummyClass.getInstance(element)).toEqual(instance)
|
||||
expect(DummyClass.getInstance(element)).toBeInstanceOf(DummyClass)
|
||||
})
|
||||
|
||||
it('should accept element, either passed as a CSS selector, jQuery element, or DOM element', () => {
|
||||
createInstance()
|
||||
|
||||
expect(DummyClass.getInstance('#foo')).toEqual(instance)
|
||||
expect(DummyClass.getInstance(element)).toEqual(instance)
|
||||
|
||||
const fakejQueryObject = {
|
||||
0: element,
|
||||
jquery: 'foo'
|
||||
}
|
||||
|
||||
expect(DummyClass.getInstance(fakejQueryObject)).toEqual(instance)
|
||||
})
|
||||
|
||||
it('should return null when there is no instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(DummyClass.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return an instance', () => {
|
||||
createInstance()
|
||||
|
||||
expect(DummyClass.getOrCreateInstance(element)).toEqual(instance)
|
||||
expect(DummyClass.getInstance(element)).toEqual(DummyClass.getOrCreateInstance(element, {}))
|
||||
expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div id="foo"></div>'
|
||||
element = fixtureEl.querySelector('#foo')
|
||||
|
||||
expect(DummyClass.getInstance(element)).toBeNull()
|
||||
expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
183
bootstrap/js/tests/unit/button.spec.js
Normal file
183
bootstrap/js/tests/unit/button.spec.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
import Button from '../../src/button.js'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js'
|
||||
|
||||
describe('Button', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<button data-bs-toggle="button">Placeholder</button>'
|
||||
const buttonEl = fixtureEl.querySelector('[data-bs-toggle="button"]')
|
||||
const buttonBySelector = new Button('[data-bs-toggle="button"]')
|
||||
const buttonByElement = new Button(buttonEl)
|
||||
|
||||
expect(buttonBySelector._element).toEqual(buttonEl)
|
||||
expect(buttonByElement._element).toEqual(buttonEl)
|
||||
})
|
||||
|
||||
describe('VERSION', () => {
|
||||
it('should return plugin version', () => {
|
||||
expect(Button.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(Button.DATA_KEY).toEqual('bs.button')
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-api', () => {
|
||||
it('should toggle active class on click', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button class="btn" data-bs-toggle="button">btn</button>',
|
||||
'<button class="btn testParent" data-bs-toggle="button"><div class="test"></div></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
const btnTestParent = fixtureEl.querySelector('.testParent')
|
||||
|
||||
expect(btn).not.toHaveClass('active')
|
||||
|
||||
btn.click()
|
||||
|
||||
expect(btn).toHaveClass('active')
|
||||
|
||||
btn.click()
|
||||
|
||||
expect(btn).not.toHaveClass('active')
|
||||
|
||||
divTest.click()
|
||||
|
||||
expect(btnTestParent).toHaveClass('active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('toggle', () => {
|
||||
it('should toggle aria-pressed', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button" aria-pressed="false"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
const button = new Button(btnEl)
|
||||
|
||||
expect(btnEl.getAttribute('aria-pressed')).toEqual('false')
|
||||
expect(btnEl).not.toHaveClass('active')
|
||||
|
||||
button.toggle()
|
||||
|
||||
expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
|
||||
expect(btnEl).toHaveClass('active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose a button', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
const button = new Button(btnEl)
|
||||
|
||||
expect(Button.getInstance(btnEl)).not.toBeNull()
|
||||
|
||||
button.dispose()
|
||||
|
||||
expect(Button.getInstance(btnEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('jQueryInterface', () => {
|
||||
it('should handle config passed and toggle existing button', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
const button = new Button(btnEl)
|
||||
|
||||
const spy = spyOn(button, 'toggle')
|
||||
|
||||
jQueryMock.fn.button = Button.jQueryInterface
|
||||
jQueryMock.elements = [btnEl]
|
||||
|
||||
jQueryMock.fn.button.call(jQueryMock, 'toggle')
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create new button instance and call toggle', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
|
||||
jQueryMock.fn.button = Button.jQueryInterface
|
||||
jQueryMock.elements = [btnEl]
|
||||
|
||||
jQueryMock.fn.button.call(jQueryMock, 'toggle')
|
||||
|
||||
expect(Button.getInstance(btnEl)).not.toBeNull()
|
||||
expect(btnEl).toHaveClass('active')
|
||||
})
|
||||
|
||||
it('should just create a button instance without calling toggle', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
|
||||
jQueryMock.fn.button = Button.jQueryInterface
|
||||
jQueryMock.elements = [btnEl]
|
||||
|
||||
jQueryMock.fn.button.call(jQueryMock)
|
||||
|
||||
expect(Button.getInstance(btnEl)).not.toBeNull()
|
||||
expect(btnEl).not.toHaveClass('active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const button = new Button(div)
|
||||
|
||||
expect(Button.getInstance(div)).toEqual(button)
|
||||
expect(Button.getInstance(div)).toBeInstanceOf(Button)
|
||||
})
|
||||
|
||||
it('should return null when there is no button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Button.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const button = new Button(div)
|
||||
|
||||
expect(Button.getOrCreateInstance(div)).toEqual(button)
|
||||
expect(Button.getInstance(div)).toEqual(Button.getOrCreateInstance(div, {}))
|
||||
expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Button.getInstance(div)).toBeNull()
|
||||
expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
|
||||
})
|
||||
})
|
||||
})
|
1572
bootstrap/js/tests/unit/carousel.spec.js
Normal file
1572
bootstrap/js/tests/unit/carousel.spec.js
Normal file
File diff suppressed because it is too large
Load diff
1062
bootstrap/js/tests/unit/collapse.spec.js
Normal file
1062
bootstrap/js/tests/unit/collapse.spec.js
Normal file
File diff suppressed because it is too large
Load diff
104
bootstrap/js/tests/unit/dom/data.spec.js
Normal file
104
bootstrap/js/tests/unit/dom/data.spec.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
import Data from '../../../src/dom/data.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('Data', () => {
|
||||
const TEST_KEY = 'bs.test'
|
||||
const UNKNOWN_KEY = 'bs.unknown'
|
||||
const TEST_DATA = {
|
||||
test: 'bsData'
|
||||
}
|
||||
|
||||
let fixtureEl
|
||||
let div
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
div = fixtureEl.querySelector('div')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Data.remove(div, TEST_KEY)
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should return null for unknown elements', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
|
||||
expect(Data.get(null)).toBeNull()
|
||||
expect(Data.get(undefined)).toBeNull()
|
||||
expect(Data.get(document.createElement('div'), TEST_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null for unknown keys', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
|
||||
expect(Data.get(div, null)).toBeNull()
|
||||
expect(Data.get(div, undefined)).toBeNull()
|
||||
expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
it('should store data for an element with a given key and return it', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
|
||||
expect(Data.get(div, TEST_KEY)).toEqual(data)
|
||||
})
|
||||
|
||||
it('should overwrite data if something is already stored', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
const copy = { ...data }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.set(div, TEST_KEY, copy)
|
||||
|
||||
// Using `toBe` since spread creates a shallow copy
|
||||
expect(Data.get(div, TEST_KEY)).not.toBe(data)
|
||||
expect(Data.get(div, TEST_KEY)).toBe(copy)
|
||||
})
|
||||
|
||||
it('should do nothing when an element has nothing stored', () => {
|
||||
Data.remove(div, TEST_KEY)
|
||||
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should remove nothing for an unknown key', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.remove(div, UNKNOWN_KEY)
|
||||
|
||||
expect(Data.get(div, TEST_KEY)).toEqual(data)
|
||||
})
|
||||
|
||||
it('should remove data for a given key', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.remove(div, TEST_KEY)
|
||||
|
||||
expect(Data.get(div, TEST_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
it('should console.error a message if called with multiple keys', () => {
|
||||
console.error = jasmine.createSpy('console.error')
|
||||
|
||||
const data = { ...TEST_DATA }
|
||||
const copy = { ...data }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.set(div, UNKNOWN_KEY, copy)
|
||||
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
|
||||
})
|
||||
})
|
480
bootstrap/js/tests/unit/dom/event-handler.spec.js
Normal file
480
bootstrap/js/tests/unit/dom/event-handler.spec.js
Normal file
|
@ -0,0 +1,480 @@
|
|||
import EventHandler from '../../../src/dom/event-handler.js'
|
||||
import { noop } from '../../../src/util/index.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('EventHandler', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('on', () => {
|
||||
it('should not add event listener if the event is not a string', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, null, noop)
|
||||
EventHandler.on(null, 'click', noop)
|
||||
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should add event listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, 'click', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
div.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should add namespaced event listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, 'bs.namespace', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
EventHandler.trigger(div, 'bs.namespace')
|
||||
})
|
||||
})
|
||||
|
||||
it('should add native namespaced event listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, 'click.namespace', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
EventHandler.trigger(div, 'click')
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle event delegation', () => {
|
||||
return new Promise(resolve => {
|
||||
EventHandler.on(document, 'click', '.test', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
fixtureEl.innerHTML = '<div class="test"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
div.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle mouseenter/mouseleave like the native counterpart', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="outer">',
|
||||
'<div class="inner">',
|
||||
'<div class="nested">',
|
||||
'<div class="deep"></div>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<div class="sibling"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const outer = fixtureEl.querySelector('.outer')
|
||||
const inner = fixtureEl.querySelector('.inner')
|
||||
const nested = fixtureEl.querySelector('.nested')
|
||||
const deep = fixtureEl.querySelector('.deep')
|
||||
const sibling = fixtureEl.querySelector('.sibling')
|
||||
|
||||
const enterSpy = jasmine.createSpy('mouseenter')
|
||||
const leaveSpy = jasmine.createSpy('mouseleave')
|
||||
const delegateEnterSpy = jasmine.createSpy('mouseenter')
|
||||
const delegateLeaveSpy = jasmine.createSpy('mouseleave')
|
||||
|
||||
EventHandler.on(inner, 'mouseenter', enterSpy)
|
||||
EventHandler.on(inner, 'mouseleave', leaveSpy)
|
||||
EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy)
|
||||
EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)
|
||||
|
||||
EventHandler.on(sibling, 'mouseenter', () => {
|
||||
expect(enterSpy.calls.count()).toEqual(2)
|
||||
expect(leaveSpy.calls.count()).toEqual(2)
|
||||
expect(delegateEnterSpy.calls.count()).toEqual(2)
|
||||
expect(delegateLeaveSpy.calls.count()).toEqual(2)
|
||||
resolve()
|
||||
})
|
||||
|
||||
const moveMouse = (from, to) => {
|
||||
from.dispatchEvent(new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
relatedTarget: to
|
||||
}))
|
||||
|
||||
to.dispatchEvent(new MouseEvent('mouseover', {
|
||||
bubbles: true,
|
||||
relatedTarget: from
|
||||
}))
|
||||
}
|
||||
|
||||
// from outer to deep and back to outer (nested)
|
||||
moveMouse(outer, inner)
|
||||
moveMouse(inner, nested)
|
||||
moveMouse(nested, deep)
|
||||
moveMouse(deep, nested)
|
||||
moveMouse(nested, inner)
|
||||
moveMouse(inner, outer)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(enterSpy.calls.count()).toEqual(1)
|
||||
expect(leaveSpy.calls.count()).toEqual(1)
|
||||
expect(delegateEnterSpy.calls.count()).toEqual(1)
|
||||
expect(delegateLeaveSpy.calls.count()).toEqual(1)
|
||||
|
||||
// from outer to inner to sibling (adjacent)
|
||||
moveMouse(outer, inner)
|
||||
moveMouse(inner, sibling)
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('one', () => {
|
||||
it('should call listener just once', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
let called = 0
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const obj = {
|
||||
oneListener() {
|
||||
called++
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.one(div, 'bootstrap', obj.oneListener)
|
||||
|
||||
EventHandler.trigger(div, 'bootstrap')
|
||||
EventHandler.trigger(div, 'bootstrap')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(1)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should call delegated listener just once', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
let called = 0
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const obj = {
|
||||
oneListener() {
|
||||
called++
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener)
|
||||
|
||||
EventHandler.trigger(div, 'bootstrap')
|
||||
EventHandler.trigger(div, 'bootstrap')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(1)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('off', () => {
|
||||
it('should not remove a listener', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.off(div, null, noop)
|
||||
EventHandler.off(null, 'click', noop)
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should remove a listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
let called = 0
|
||||
const handler = () => {
|
||||
called++
|
||||
}
|
||||
|
||||
EventHandler.on(div, 'foobar', handler)
|
||||
EventHandler.trigger(div, 'foobar')
|
||||
|
||||
EventHandler.off(div, 'foobar', handler)
|
||||
EventHandler.trigger(div, 'foobar')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(1)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove all the events', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
let called = 0
|
||||
|
||||
EventHandler.on(div, 'foobar', () => {
|
||||
called++
|
||||
})
|
||||
EventHandler.on(div, 'foobar', () => {
|
||||
called++
|
||||
})
|
||||
EventHandler.trigger(div, 'foobar')
|
||||
|
||||
EventHandler.off(div, 'foobar')
|
||||
EventHandler.trigger(div, 'foobar')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(2)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove all the namespaced listeners if namespace is passed', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
let called = 0
|
||||
|
||||
EventHandler.on(div, 'foobar.namespace', () => {
|
||||
called++
|
||||
})
|
||||
EventHandler.on(div, 'foofoo.namespace', () => {
|
||||
called++
|
||||
})
|
||||
EventHandler.trigger(div, 'foobar.namespace')
|
||||
EventHandler.trigger(div, 'foofoo.namespace')
|
||||
|
||||
EventHandler.off(div, '.namespace')
|
||||
EventHandler.trigger(div, 'foobar.namespace')
|
||||
EventHandler.trigger(div, 'foofoo.namespace')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(2)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the namespaced listeners', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
let calledCallback1 = 0
|
||||
let calledCallback2 = 0
|
||||
|
||||
EventHandler.on(div, 'foobar.namespace', () => {
|
||||
calledCallback1++
|
||||
})
|
||||
EventHandler.on(div, 'foofoo.namespace', () => {
|
||||
calledCallback2++
|
||||
})
|
||||
|
||||
EventHandler.trigger(div, 'foobar.namespace')
|
||||
EventHandler.off(div, 'foobar.namespace')
|
||||
EventHandler.trigger(div, 'foobar.namespace')
|
||||
|
||||
EventHandler.trigger(div, 'foofoo.namespace')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(calledCallback1).toEqual(1)
|
||||
expect(calledCallback2).toEqual(1)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the all the namespaced listeners for native events', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
let called = 0
|
||||
|
||||
EventHandler.on(div, 'click.namespace', () => {
|
||||
called++
|
||||
})
|
||||
EventHandler.on(div, 'click.namespace2', () => {
|
||||
called++
|
||||
})
|
||||
|
||||
EventHandler.trigger(div, 'click')
|
||||
EventHandler.off(div, 'click')
|
||||
EventHandler.trigger(div, 'click')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(2)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the specified namespaced listeners for native events', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
let called1 = 0
|
||||
let called2 = 0
|
||||
|
||||
EventHandler.on(div, 'click.namespace', () => {
|
||||
called1++
|
||||
})
|
||||
EventHandler.on(div, 'click.namespace2', () => {
|
||||
called2++
|
||||
})
|
||||
EventHandler.trigger(div, 'click')
|
||||
|
||||
EventHandler.off(div, 'click.namespace')
|
||||
EventHandler.trigger(div, 'click')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called1).toEqual(1)
|
||||
expect(called2).toEqual(2)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove a listener registered by .one', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const handler = () => {
|
||||
reject(new Error('called'))
|
||||
}
|
||||
|
||||
EventHandler.one(div, 'foobar', handler)
|
||||
EventHandler.off(div, 'foobar', handler)
|
||||
|
||||
EventHandler.trigger(div, 'foobar')
|
||||
setTimeout(() => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the correct delegated event listener', () => {
|
||||
const element = document.createElement('div')
|
||||
const subelement = document.createElement('span')
|
||||
element.append(subelement)
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
element.append(anchor)
|
||||
|
||||
let i = 0
|
||||
const handler = () => {
|
||||
i++
|
||||
}
|
||||
|
||||
EventHandler.on(element, 'click', 'a', handler)
|
||||
EventHandler.on(element, 'click', 'span', handler)
|
||||
|
||||
fixtureEl.append(element)
|
||||
|
||||
EventHandler.trigger(anchor, 'click')
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// first listeners called
|
||||
expect(i).toEqual(2)
|
||||
|
||||
EventHandler.off(element, 'click', 'span', handler)
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// removed listener not called
|
||||
expect(i).toEqual(2)
|
||||
|
||||
EventHandler.trigger(anchor, 'click')
|
||||
|
||||
// not removed listener called
|
||||
expect(i).toEqual(3)
|
||||
|
||||
EventHandler.on(element, 'click', 'span', handler)
|
||||
EventHandler.trigger(anchor, 'click')
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// listener re-registered
|
||||
expect(i).toEqual(5)
|
||||
|
||||
EventHandler.off(element, 'click', 'span')
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// listener removed again
|
||||
expect(i).toEqual(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('general functionality', () => {
|
||||
it('should hydrate properties, and make them configurable', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="div1">',
|
||||
' <div id="div2"></div>',
|
||||
' <div id="div3"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div1 = fixtureEl.querySelector('#div1')
|
||||
const div2 = fixtureEl.querySelector('#div2')
|
||||
|
||||
EventHandler.on(div1, 'click', event => {
|
||||
expect(event.currentTarget).toBe(div2)
|
||||
expect(event.delegateTarget).toBe(div1)
|
||||
expect(event.originalTarget).toBeNull()
|
||||
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
configurable: true,
|
||||
get() {
|
||||
return div1
|
||||
}
|
||||
})
|
||||
|
||||
expect(event.currentTarget).toBe(div1)
|
||||
resolve()
|
||||
})
|
||||
|
||||
expect(() => {
|
||||
EventHandler.trigger(div1, 'click', { originalTarget: null, currentTarget: div2 })
|
||||
}).not.toThrowError(TypeError)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
135
bootstrap/js/tests/unit/dom/manipulator.spec.js
Normal file
135
bootstrap/js/tests/unit/dom/manipulator.spec.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
import Manipulator from '../../../src/dom/manipulator.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('Manipulator', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('setDataAttribute', () => {
|
||||
it('should set data attribute prefixed with bs', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.setDataAttribute(div, 'key', 'value')
|
||||
expect(div.getAttribute('data-bs-key')).toEqual('value')
|
||||
})
|
||||
|
||||
it('should set data attribute in kebab case', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.setDataAttribute(div, 'testKey', 'value')
|
||||
expect(div.getAttribute('data-bs-test-key')).toEqual('value')
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeDataAttribute', () => {
|
||||
it('should only remove bs-prefixed data attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-key="value" data-key-bs="postfixed" data-key="value"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.removeDataAttribute(div, 'key')
|
||||
expect(div.getAttribute('data-bs-key')).toBeNull()
|
||||
expect(div.getAttribute('data-key-bs')).toEqual('postfixed')
|
||||
expect(div.getAttribute('data-key')).toEqual('value')
|
||||
})
|
||||
|
||||
it('should remove data attribute in kebab case', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test-key="value"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.removeDataAttribute(div, 'testKey')
|
||||
expect(div.getAttribute('data-bs-test-key')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDataAttributes', () => {
|
||||
it('should return an empty object for null', () => {
|
||||
expect(Manipulator.getDataAttributes(null)).toEqual({})
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should get only bs-prefixed data attributes without bs namespace', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="tabs" data-bs-target="#element" data-another="value" data-target-bs="#element" data-in-bs-out="in-between"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttributes(div)).toEqual({
|
||||
toggle: 'tabs',
|
||||
target: '#element'
|
||||
})
|
||||
})
|
||||
|
||||
it('should omit `bs-config` data attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="tabs" data-bs-target="#element" data-bs-config=\'{"testBool":false}\'></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttributes(div)).toEqual({
|
||||
toggle: 'tabs',
|
||||
target: '#element'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDataAttribute', () => {
|
||||
it('should only get bs-prefixed data attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-key="value" data-test-bs="postFixed" data-toggle="tab"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'key')).toEqual('value')
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toBeNull()
|
||||
expect(Manipulator.getDataAttribute(div, 'toggle')).toBeNull()
|
||||
})
|
||||
|
||||
it('should get data attribute in kebab case', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test-key="value" ></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'testKey')).toEqual('value')
|
||||
})
|
||||
|
||||
it('should normalize data', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test="false" ></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toBeFalse()
|
||||
|
||||
div.setAttribute('data-bs-test', 'true')
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toBeTrue()
|
||||
|
||||
div.setAttribute('data-bs-test', '1')
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1)
|
||||
})
|
||||
|
||||
it('should normalize json data', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test=\'{"delay":{"show":100,"hide":10}}\'></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual({ delay: { show: 100, hide: 10 } })
|
||||
|
||||
const objectData = { 'Super Hero': ['Iron Man', 'Super Man'], testNum: 90, url: 'http://localhost:8080/test?foo=bar' }
|
||||
const dataStr = JSON.stringify(objectData)
|
||||
div.setAttribute('data-bs-test', encodeURIComponent(dataStr))
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
|
||||
|
||||
div.setAttribute('data-bs-test', dataStr)
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
|
||||
})
|
||||
})
|
||||
})
|
414
bootstrap/js/tests/unit/dom/selector-engine.spec.js
Normal file
414
bootstrap/js/tests/unit/dom/selector-engine.spec.js
Normal file
|
@ -0,0 +1,414 @@
|
|||
import SelectorEngine from '../../../src/dom/selector-engine.js'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture.js'
|
||||
|
||||
describe('SelectorEngine', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('find', () => {
|
||||
it('should find elements', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.find('div', fixtureEl)).toEqual([div])
|
||||
})
|
||||
|
||||
it('should find elements globally', () => {
|
||||
fixtureEl.innerHTML = '<div id="test"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.find('#test')).toEqual([div])
|
||||
})
|
||||
|
||||
it('should handle :scope selectors', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<ul>',
|
||||
' <li></li>',
|
||||
' <li>',
|
||||
' <a href="#" class="active">link</a>',
|
||||
' </li>',
|
||||
' <li></li>',
|
||||
'</ul>'
|
||||
].join('')
|
||||
|
||||
const listEl = fixtureEl.querySelector('ul')
|
||||
const aActive = fixtureEl.querySelector('.active')
|
||||
|
||||
expect(SelectorEngine.find(':scope > li > .active', listEl)).toEqual([aActive])
|
||||
})
|
||||
})
|
||||
|
||||
describe('findOne', () => {
|
||||
it('should return one element', () => {
|
||||
fixtureEl.innerHTML = '<div id="test"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.findOne('#test')).toEqual(div)
|
||||
})
|
||||
})
|
||||
|
||||
describe('children', () => {
|
||||
it('should find children', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<ul>',
|
||||
' <li></li>',
|
||||
' <li></li>',
|
||||
' <li></li>',
|
||||
'</ul>'
|
||||
].join('')
|
||||
|
||||
const list = fixtureEl.querySelector('ul')
|
||||
const liList = [].concat(...fixtureEl.querySelectorAll('li'))
|
||||
const result = SelectorEngine.children(list, 'li')
|
||||
|
||||
expect(result).toEqual(liList)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parents', () => {
|
||||
it('should return parents', () => {
|
||||
expect(SelectorEngine.parents(fixtureEl, 'body')).toHaveSize(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('prev', () => {
|
||||
it('should return previous element', () => {
|
||||
fixtureEl.innerHTML = '<div class="test"></div><button class="btn"></button>'
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])
|
||||
})
|
||||
|
||||
it('should return previous element with an extra element between', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="test"></div>',
|
||||
'<span></span>',
|
||||
'<button class="btn"></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])
|
||||
})
|
||||
|
||||
it('should return previous element with comments or text nodes between', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="test"></div>',
|
||||
'<div class="test"></div>',
|
||||
'<!-- Comment-->',
|
||||
'Text',
|
||||
'<button class="btn"></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelectorAll('.test')[1]
|
||||
|
||||
expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])
|
||||
})
|
||||
})
|
||||
|
||||
describe('next', () => {
|
||||
it('should return next element', () => {
|
||||
fixtureEl.innerHTML = '<div class="test"></div><button class="btn"></button>'
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
|
||||
})
|
||||
|
||||
it('should return next element with an extra element between', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="test"></div>',
|
||||
'<span></span>',
|
||||
'<button class="btn"></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
|
||||
})
|
||||
|
||||
it('should return next element with comments or text nodes between', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="test"></div>',
|
||||
'<!-- Comment-->',
|
||||
'Text',
|
||||
'<button class="btn"></button>',
|
||||
'<button class="btn"></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
|
||||
})
|
||||
})
|
||||
|
||||
describe('focusableChildren', () => {
|
||||
it('should return only elements with specific tag names', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>lorem</div>',
|
||||
'<span>lorem</span>',
|
||||
'<a>lorem</a>',
|
||||
'<button>lorem</button>',
|
||||
'<input>',
|
||||
'<textarea></textarea>',
|
||||
'<select></select>',
|
||||
'<details>lorem</details>'
|
||||
].join('')
|
||||
|
||||
const expectedElements = [
|
||||
fixtureEl.querySelector('a'),
|
||||
fixtureEl.querySelector('button'),
|
||||
fixtureEl.querySelector('input'),
|
||||
fixtureEl.querySelector('textarea'),
|
||||
fixtureEl.querySelector('select'),
|
||||
fixtureEl.querySelector('details')
|
||||
]
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should return any element with non negative tab index', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div tabindex>lorem</div>',
|
||||
'<div tabindex="0">lorem</div>',
|
||||
'<div tabindex="10">lorem</div>'
|
||||
].join('')
|
||||
|
||||
const expectedElements = [
|
||||
fixtureEl.querySelector('[tabindex]'),
|
||||
fixtureEl.querySelector('[tabindex="0"]'),
|
||||
fixtureEl.querySelector('[tabindex="10"]')
|
||||
]
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should return not return elements with negative tab index', () => {
|
||||
fixtureEl.innerHTML = '<button tabindex="-1">lorem</button>'
|
||||
|
||||
const expectedElements = []
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should return contenteditable elements', () => {
|
||||
fixtureEl.innerHTML = '<div contenteditable="true">lorem</div>'
|
||||
|
||||
const expectedElements = [fixtureEl.querySelector('[contenteditable="true"]')]
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should not return disabled elements', () => {
|
||||
fixtureEl.innerHTML = '<button disabled="true">lorem</button>'
|
||||
|
||||
const expectedElements = []
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should not return invisible elements', () => {
|
||||
fixtureEl.innerHTML = '<button style="display:none;">lorem</button>'
|
||||
|
||||
const expectedElements = []
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSelectorFromElement', () => {
|
||||
it('should get selector from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should get selector from href if data-bs-target equal to #', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('.target')
|
||||
})
|
||||
|
||||
it('should return null if a selector from a href is a url without an anchor', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the anchor if a selector from a href is a url', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
|
||||
'<div id="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toEqual('#target')
|
||||
})
|
||||
|
||||
it('should return null if selector not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getElementFromSelector', () => {
|
||||
it('should get element from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should get element from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
|
||||
})
|
||||
|
||||
it('should return null if element not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getElementFromSelector(testEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMultipleElementsFromSelector', () => {
|
||||
it('should get elements from data-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target=".target"></div>',
|
||||
'<div class="target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements if several ids are given', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target="#target1,#target2"></div>',
|
||||
'<div class="target" id="target1"></div>',
|
||||
'<div class="target" id="target2"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements if several ids with special chars are given', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-target="#j_id11:exampleModal,#j_id22:exampleModal"></div>',
|
||||
'<div class="target" id="j_id11:exampleModal"></div>',
|
||||
'<div class="target" id="j_id22:exampleModal"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements in array, from href if no data-bs-target set', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" href=".target"></a>',
|
||||
'<div class="target"></div>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should return empty array if elements not found', () => {
|
||||
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
|
||||
})
|
||||
|
||||
it('should return empty array if no selector', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const testEl = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toHaveSize(0)
|
||||
})
|
||||
})
|
||||
})
|
2433
bootstrap/js/tests/unit/dropdown.spec.js
Normal file
2433
bootstrap/js/tests/unit/dropdown.spec.js
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue