You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

687 lines
22 KiB

  1. /*!
  2. * Bootstrap dropdown.js v5.1.3 (https://getbootstrap.com/)
  3. * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
  4. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./base-component.js')) :
  8. typeof define === 'function' && define.amd ? define(['@popperjs/core', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './base-component'], factory) :
  9. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global.Popper, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Base));
  10. })(this, (function (Popper, EventHandler, Manipulator, SelectorEngine, BaseComponent) { 'use strict';
  11. const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };
  12. function _interopNamespace(e) {
  13. if (e && e.__esModule) return e;
  14. const n = Object.create(null);
  15. if (e) {
  16. for (const k in e) {
  17. if (k !== 'default') {
  18. const d = Object.getOwnPropertyDescriptor(e, k);
  19. Object.defineProperty(n, k, d.get ? d : {
  20. enumerable: true,
  21. get: () => e[k]
  22. });
  23. }
  24. }
  25. }
  26. n.default = e;
  27. return Object.freeze(n);
  28. }
  29. const Popper__namespace = /*#__PURE__*/_interopNamespace(Popper);
  30. const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);
  31. const Manipulator__default = /*#__PURE__*/_interopDefaultLegacy(Manipulator);
  32. const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);
  33. const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);
  34. /**
  35. * --------------------------------------------------------------------------
  36. * Bootstrap (v5.1.3): util/index.js
  37. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  38. * --------------------------------------------------------------------------
  39. */
  40. const toType = obj => {
  41. if (obj === null || obj === undefined) {
  42. return `${obj}`;
  43. }
  44. return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase();
  45. };
  46. const getSelector = element => {
  47. let selector = element.getAttribute('data-bs-target');
  48. if (!selector || selector === '#') {
  49. let hrefAttr = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,
  50. // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
  51. // `document.querySelector` will rightfully complain it is invalid.
  52. // See https://github.com/twbs/bootstrap/issues/32273
  53. if (!hrefAttr || !hrefAttr.includes('#') && !hrefAttr.startsWith('.')) {
  54. return null;
  55. } // Just in case some CMS puts out a full URL with the anchor appended
  56. if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {
  57. hrefAttr = `#${hrefAttr.split('#')[1]}`;
  58. }
  59. selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null;
  60. }
  61. return selector;
  62. };
  63. const getElementFromSelector = element => {
  64. const selector = getSelector(element);
  65. return selector ? document.querySelector(selector) : null;
  66. };
  67. const isElement = obj => {
  68. if (!obj || typeof obj !== 'object') {
  69. return false;
  70. }
  71. if (typeof obj.jquery !== 'undefined') {
  72. obj = obj[0];
  73. }
  74. return typeof obj.nodeType !== 'undefined';
  75. };
  76. const getElement = obj => {
  77. if (isElement(obj)) {
  78. // it's a jQuery object or a node element
  79. return obj.jquery ? obj[0] : obj;
  80. }
  81. if (typeof obj === 'string' && obj.length > 0) {
  82. return document.querySelector(obj);
  83. }
  84. return null;
  85. };
  86. const typeCheckConfig = (componentName, config, configTypes) => {
  87. Object.keys(configTypes).forEach(property => {
  88. const expectedTypes = configTypes[property];
  89. const value = config[property];
  90. const valueType = value && isElement(value) ? 'element' : toType(value);
  91. if (!new RegExp(expectedTypes).test(valueType)) {
  92. throw new TypeError(`${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`);
  93. }
  94. });
  95. };
  96. const isVisible = element => {
  97. if (!isElement(element) || element.getClientRects().length === 0) {
  98. return false;
  99. }
  100. return getComputedStyle(element).getPropertyValue('visibility') === 'visible';
  101. };
  102. const isDisabled = element => {
  103. if (!element || element.nodeType !== Node.ELEMENT_NODE) {
  104. return true;
  105. }
  106. if (element.classList.contains('disabled')) {
  107. return true;
  108. }
  109. if (typeof element.disabled !== 'undefined') {
  110. return element.disabled;
  111. }
  112. return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';
  113. };
  114. const noop = () => {};
  115. const getjQuery = () => {
  116. const {
  117. jQuery
  118. } = window;
  119. if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
  120. return jQuery;
  121. }
  122. return null;
  123. };
  124. const DOMContentLoadedCallbacks = [];
  125. const onDOMContentLoaded = callback => {
  126. if (document.readyState === 'loading') {
  127. // add listener on the first call when the document is in loading state
  128. if (!DOMContentLoadedCallbacks.length) {
  129. document.addEventListener('DOMContentLoaded', () => {
  130. DOMContentLoadedCallbacks.forEach(callback => callback());
  131. });
  132. }
  133. DOMContentLoadedCallbacks.push(callback);
  134. } else {
  135. callback();
  136. }
  137. };
  138. const isRTL = () => document.documentElement.dir === 'rtl';
  139. const defineJQueryPlugin = plugin => {
  140. onDOMContentLoaded(() => {
  141. const $ = getjQuery();
  142. /* istanbul ignore if */
  143. if ($) {
  144. const name = plugin.NAME;
  145. const JQUERY_NO_CONFLICT = $.fn[name];
  146. $.fn[name] = plugin.jQueryInterface;
  147. $.fn[name].Constructor = plugin;
  148. $.fn[name].noConflict = () => {
  149. $.fn[name] = JQUERY_NO_CONFLICT;
  150. return plugin.jQueryInterface;
  151. };
  152. }
  153. });
  154. };
  155. /**
  156. * Return the previous/next element of a list.
  157. *
  158. * @param {array} list The list of elements
  159. * @param activeElement The active element
  160. * @param shouldGetNext Choose to get next or previous element
  161. * @param isCycleAllowed
  162. * @return {Element|elem} The proper element
  163. */
  164. const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
  165. 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
  166. if (index === -1) {
  167. return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0];
  168. }
  169. const listLength = list.length;
  170. index += shouldGetNext ? 1 : -1;
  171. if (isCycleAllowed) {
  172. index = (index + listLength) % listLength;
  173. }
  174. return list[Math.max(0, Math.min(index, listLength - 1))];
  175. };
  176. /**
  177. * --------------------------------------------------------------------------
  178. * Bootstrap (v5.1.3): dropdown.js
  179. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  180. * --------------------------------------------------------------------------
  181. */
  182. /**
  183. * ------------------------------------------------------------------------
  184. * Constants
  185. * ------------------------------------------------------------------------
  186. */
  187. const NAME = 'dropdown';
  188. const DATA_KEY = 'bs.dropdown';
  189. const EVENT_KEY = `.${DATA_KEY}`;
  190. const DATA_API_KEY = '.data-api';
  191. const ESCAPE_KEY = 'Escape';
  192. const SPACE_KEY = 'Space';
  193. const TAB_KEY = 'Tab';
  194. const ARROW_UP_KEY = 'ArrowUp';
  195. const ARROW_DOWN_KEY = 'ArrowDown';
  196. const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button
  197. const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`);
  198. const EVENT_HIDE = `hide${EVENT_KEY}`;
  199. const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
  200. const EVENT_SHOW = `show${EVENT_KEY}`;
  201. const EVENT_SHOWN = `shown${EVENT_KEY}`;
  202. const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
  203. const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;
  204. const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
  205. const CLASS_NAME_SHOW = 'show';
  206. const CLASS_NAME_DROPUP = 'dropup';
  207. const CLASS_NAME_DROPEND = 'dropend';
  208. const CLASS_NAME_DROPSTART = 'dropstart';
  209. const CLASS_NAME_NAVBAR = 'navbar';
  210. const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]';
  211. const SELECTOR_MENU = '.dropdown-menu';
  212. const SELECTOR_NAVBAR_NAV = '.navbar-nav';
  213. const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
  214. const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';
  215. const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';
  216. const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';
  217. const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';
  218. const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';
  219. const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';
  220. const Default = {
  221. offset: [0, 2],
  222. boundary: 'clippingParents',
  223. reference: 'toggle',
  224. display: 'dynamic',
  225. popperConfig: null,
  226. autoClose: true
  227. };
  228. const DefaultType = {
  229. offset: '(array|string|function)',
  230. boundary: '(string|element)',
  231. reference: '(string|element|object)',
  232. display: 'string',
  233. popperConfig: '(null|object|function)',
  234. autoClose: '(boolean|string)'
  235. };
  236. /**
  237. * ------------------------------------------------------------------------
  238. * Class Definition
  239. * ------------------------------------------------------------------------
  240. */
  241. class Dropdown extends BaseComponent__default.default {
  242. constructor(element, config) {
  243. super(element);
  244. this._popper = null;
  245. this._config = this._getConfig(config);
  246. this._menu = this._getMenuElement();
  247. this._inNavbar = this._detectNavbar();
  248. } // Getters
  249. static get Default() {
  250. return Default;
  251. }
  252. static get DefaultType() {
  253. return DefaultType;
  254. }
  255. static get NAME() {
  256. return NAME;
  257. } // Public
  258. toggle() {
  259. return this._isShown() ? this.hide() : this.show();
  260. }
  261. show() {
  262. if (isDisabled(this._element) || this._isShown(this._menu)) {
  263. return;
  264. }
  265. const relatedTarget = {
  266. relatedTarget: this._element
  267. };
  268. const showEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW, relatedTarget);
  269. if (showEvent.defaultPrevented) {
  270. return;
  271. }
  272. const parent = Dropdown.getParentFromElement(this._element); // Totally disable Popper for Dropdowns in Navbar
  273. if (this._inNavbar) {
  274. Manipulator__default.default.setDataAttribute(this._menu, 'popper', 'none');
  275. } else {
  276. this._createPopper(parent);
  277. } // If this is a touch-enabled device we add extra
  278. // empty mouseover listeners to the body's immediate children;
  279. // only needed because of broken event delegation on iOS
  280. // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
  281. if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) {
  282. [].concat(...document.body.children).forEach(elem => EventHandler__default.default.on(elem, 'mouseover', noop));
  283. }
  284. this._element.focus();
  285. this._element.setAttribute('aria-expanded', true);
  286. this._menu.classList.add(CLASS_NAME_SHOW);
  287. this._element.classList.add(CLASS_NAME_SHOW);
  288. EventHandler__default.default.trigger(this._element, EVENT_SHOWN, relatedTarget);
  289. }
  290. hide() {
  291. if (isDisabled(this._element) || !this._isShown(this._menu)) {
  292. return;
  293. }
  294. const relatedTarget = {
  295. relatedTarget: this._element
  296. };
  297. this._completeHide(relatedTarget);
  298. }
  299. dispose() {
  300. if (this._popper) {
  301. this._popper.destroy();
  302. }
  303. super.dispose();
  304. }
  305. update() {
  306. this._inNavbar = this._detectNavbar();
  307. if (this._popper) {
  308. this._popper.update();
  309. }
  310. } // Private
  311. _completeHide(relatedTarget) {
  312. const hideEvent = EventHandler__default.default.trigger(this._element, EVENT_HIDE, relatedTarget);
  313. if (hideEvent.defaultPrevented) {
  314. return;
  315. } // If this is a touch-enabled device we remove the extra
  316. // empty mouseover listeners we added for iOS support
  317. if ('ontouchstart' in document.documentElement) {
  318. [].concat(...document.body.children).forEach(elem => EventHandler__default.default.off(elem, 'mouseover', noop));
  319. }
  320. if (this._popper) {
  321. this._popper.destroy();
  322. }
  323. this._menu.classList.remove(CLASS_NAME_SHOW);
  324. this._element.classList.remove(CLASS_NAME_SHOW);
  325. this._element.setAttribute('aria-expanded', 'false');
  326. Manipulator__default.default.removeDataAttribute(this._menu, 'popper');
  327. EventHandler__default.default.trigger(this._element, EVENT_HIDDEN, relatedTarget);
  328. }
  329. _getConfig(config) {
  330. config = { ...this.constructor.Default,
  331. ...Manipulator__default.default.getDataAttributes(this._element),
  332. ...config
  333. };
  334. typeCheckConfig(NAME, config, this.constructor.DefaultType);
  335. if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {
  336. // Popper virtual elements require a getBoundingClientRect method
  337. throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
  338. }
  339. return config;
  340. }
  341. _createPopper(parent) {
  342. if (typeof Popper__namespace === 'undefined') {
  343. throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)');
  344. }
  345. let referenceElement = this._element;
  346. if (this._config.reference === 'parent') {
  347. referenceElement = parent;
  348. } else if (isElement(this._config.reference)) {
  349. referenceElement = getElement(this._config.reference);
  350. } else if (typeof this._config.reference === 'object') {
  351. referenceElement = this._config.reference;
  352. }
  353. const popperConfig = this._getPopperConfig();
  354. const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false);
  355. this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
  356. if (isDisplayStatic) {
  357. Manipulator__default.default.setDataAttribute(this._menu, 'popper', 'static');
  358. }
  359. }
  360. _isShown(element = this._element) {
  361. return element.classList.contains(CLASS_NAME_SHOW);
  362. }
  363. _getMenuElement() {
  364. return SelectorEngine__default.default.next(this._element, SELECTOR_MENU)[0];
  365. }
  366. _getPlacement() {
  367. const parentDropdown = this._element.parentNode;
  368. if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
  369. return PLACEMENT_RIGHT;
  370. }
  371. if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
  372. return PLACEMENT_LEFT;
  373. } // We need to trim the value because custom properties can also include spaces
  374. const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';
  375. if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
  376. return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;
  377. }
  378. return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;
  379. }
  380. _detectNavbar() {
  381. return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null;
  382. }
  383. _getOffset() {
  384. const {
  385. offset
  386. } = this._config;
  387. if (typeof offset === 'string') {
  388. return offset.split(',').map(val => Number.parseInt(val, 10));
  389. }
  390. if (typeof offset === 'function') {
  391. return popperData => offset(popperData, this._element);
  392. }
  393. return offset;
  394. }
  395. _getPopperConfig() {
  396. const defaultBsPopperConfig = {
  397. placement: this._getPlacement(),
  398. modifiers: [{
  399. name: 'preventOverflow',
  400. options: {
  401. boundary: this._config.boundary
  402. }
  403. }, {
  404. name: 'offset',
  405. options: {
  406. offset: this._getOffset()
  407. }
  408. }]
  409. }; // Disable Popper if we have a static display
  410. if (this._config.display === 'static') {
  411. defaultBsPopperConfig.modifiers = [{
  412. name: 'applyStyles',
  413. enabled: false
  414. }];
  415. }
  416. return { ...defaultBsPopperConfig,
  417. ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
  418. };
  419. }
  420. _selectMenuItem({
  421. key,
  422. target
  423. }) {
  424. const items = SelectorEngine__default.default.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible);
  425. if (!items.length) {
  426. return;
  427. } // if target isn't included in items (e.g. when expanding the dropdown)
  428. // allow cycling to get the last item in case key equals ARROW_UP_KEY
  429. getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();
  430. } // Static
  431. static jQueryInterface(config) {
  432. return this.each(function () {
  433. const data = Dropdown.getOrCreateInstance(this, config);
  434. if (typeof config !== 'string') {
  435. return;
  436. }
  437. if (typeof data[config] === 'undefined') {
  438. throw new TypeError(`No method named "${config}"`);
  439. }
  440. data[config]();
  441. });
  442. }
  443. static clearMenus(event) {
  444. if (event && (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY)) {
  445. return;
  446. }
  447. const toggles = SelectorEngine__default.default.find(SELECTOR_DATA_TOGGLE);
  448. for (let i = 0, len = toggles.length; i < len; i++) {
  449. const context = Dropdown.getInstance(toggles[i]);
  450. if (!context || context._config.autoClose === false) {
  451. continue;
  452. }
  453. if (!context._isShown()) {
  454. continue;
  455. }
  456. const relatedTarget = {
  457. relatedTarget: context._element
  458. };
  459. if (event) {
  460. const composedPath = event.composedPath();
  461. const isMenuTarget = composedPath.includes(context._menu);
  462. if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {
  463. continue;
  464. } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu
  465. if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY || /input|select|option|textarea|form/i.test(event.target.tagName))) {
  466. continue;
  467. }
  468. if (event.type === 'click') {
  469. relatedTarget.clickEvent = event;
  470. }
  471. }
  472. context._completeHide(relatedTarget);
  473. }
  474. }
  475. static getParentFromElement(element) {
  476. return getElementFromSelector(element) || element.parentNode;
  477. }
  478. static dataApiKeydownHandler(event) {
  479. // If not input/textarea:
  480. // - And not a key in REGEXP_KEYDOWN => not a dropdown command
  481. // If input/textarea:
  482. // - If space key => not a dropdown command
  483. // - If key is other than escape
  484. // - If key is not up or down => not a dropdown command
  485. // - If trigger inside the menu => not a dropdown command
  486. if (/input|textarea/i.test(event.target.tagName) ? event.key === SPACE_KEY || event.key !== ESCAPE_KEY && (event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY || event.target.closest(SELECTOR_MENU)) : !REGEXP_KEYDOWN.test(event.key)) {
  487. return;
  488. }
  489. const isActive = this.classList.contains(CLASS_NAME_SHOW);
  490. if (!isActive && event.key === ESCAPE_KEY) {
  491. return;
  492. }
  493. event.preventDefault();
  494. event.stopPropagation();
  495. if (isDisabled(this)) {
  496. return;
  497. }
  498. const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine__default.default.prev(this, SELECTOR_DATA_TOGGLE)[0];
  499. const instance = Dropdown.getOrCreateInstance(getToggleButton);
  500. if (event.key === ESCAPE_KEY) {
  501. instance.hide();
  502. return;
  503. }
  504. if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) {
  505. if (!isActive) {
  506. instance.show();
  507. }
  508. instance._selectMenuItem(event);
  509. return;
  510. }
  511. if (!isActive || event.key === SPACE_KEY) {
  512. Dropdown.clearMenus();
  513. }
  514. }
  515. }
  516. /**
  517. * ------------------------------------------------------------------------
  518. * Data Api implementation
  519. * ------------------------------------------------------------------------
  520. */
  521. EventHandler__default.default.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Dropdown.dataApiKeydownHandler);
  522. EventHandler__default.default.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);
  523. EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, Dropdown.clearMenus);
  524. EventHandler__default.default.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);
  525. EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  526. event.preventDefault();
  527. Dropdown.getOrCreateInstance(this).toggle();
  528. });
  529. /**
  530. * ------------------------------------------------------------------------
  531. * jQuery
  532. * ------------------------------------------------------------------------
  533. * add .Dropdown to jQuery only if jQuery is present
  534. */
  535. defineJQueryPlugin(Dropdown);
  536. return Dropdown;
  537. }));
  538. //# sourceMappingURL=dropdown.js.map