bootstrap-submenu.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. (function(factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. // AMD. Register as an anonymous module
  4. define(['jquery'], factory);
  5. } else if (typeof exports === 'object') {
  6. // Node/CommonJS
  7. module.exports = factory(require('jquery'));
  8. } else {
  9. // Browser globals
  10. factory(jQuery);
  11. }
  12. })(function($) {
  13. class DropdownSubmenu {
  14. constructor(element) {
  15. this.element = element.parentElement;
  16. this.menuElement = this.element.querySelector('.dropdown-menu');
  17. this.init();
  18. }
  19. init() {
  20. $(this.element).off('keydown.bs.dropdown.data-api');
  21. this.menuElement.addEventListener('keydown', this.itemKeydown.bind(this));
  22. const dropdownItemNodeList = this.menuElement.querySelectorAll('.dropdown-item');
  23. for (const element of dropdownItemNodeList) {
  24. element.addEventListener('keydown', this.handleKeydownDropdownItem.bind(this));
  25. }
  26. $(this.menuElement).on('keydown', '.dropdown-submenu > .dropdown-item', this.handleKeydownSubmenuDropdownItem.bind(this));
  27. $(this.menuElement).on('click', '.dropdown-submenu > .dropdown-item', this.handleClickSubmenuDropdownItem.bind(this));
  28. $(this.element).on('hidden.bs.dropdown', () => {
  29. this.close(this.menuElement);
  30. });
  31. }
  32. handleKeydownDropdownItem(event) {
  33. // 27: Esc
  34. if (event.keyCode !== 27) {
  35. return;
  36. }
  37. event.target.closest('.dropdown-menu').previousElementSibling.focus();
  38. event.target.closest('.dropdown-menu').classList.remove('show');
  39. }
  40. handleKeydownSubmenuDropdownItem(event) {
  41. // 32: Spacebar
  42. if (event.keyCode !== 32) {
  43. return;
  44. }
  45. // NOTE: Off vertical scrolling
  46. event.preventDefault();
  47. this.toggle(event.target);
  48. }
  49. handleClickSubmenuDropdownItem(event) {
  50. event.stopPropagation();
  51. this.toggle(event.target);
  52. }
  53. itemKeydown(event) {
  54. // 38: Arrow up, 40: Arrow down
  55. if (![38, 40].includes(event.keyCode)) {
  56. return;
  57. }
  58. // NOTE: Off vertical scrolling
  59. event.preventDefault();
  60. event.stopPropagation();
  61. const itemNodeList = this.element.querySelectorAll('.show > .dropdown-item:not(:disabled):not(.disabled), .show > .dropdown > .dropdown-item');
  62. let index = Array.from(itemNodeList).findIndex((element) => {
  63. return element === event.target;
  64. });
  65. if (event.keyCode === 38 && index !== 0) {
  66. index--;
  67. } else if (event.keyCode === 40 && index !== itemNodeList.length - 1) {
  68. index++;
  69. } else {
  70. return;
  71. }
  72. itemNodeList[index].focus();
  73. }
  74. toggle(element) {
  75. const dropdownElement = element.closest('.dropdown');
  76. const parentMenuElement = dropdownElement.closest('.dropdown-menu');
  77. const menuElement = dropdownElement.querySelector('.dropdown-menu');
  78. const isOpen = menuElement.classList.contains('show');
  79. this.close(parentMenuElement);
  80. menuElement.classList.toggle('show', !isOpen);
  81. }
  82. close(menuElement) {
  83. const menuNodeList = menuElement.querySelectorAll('.dropdown-menu.show');
  84. for (const element of menuNodeList) {
  85. element.classList.remove('show');
  86. }
  87. }
  88. }
  89. // For AMD/Node/CommonJS used elements (optional)
  90. // http://learn.jquery.com/jquery-ui/environments/amd/
  91. $.fn.submenupicker = function(elements) {
  92. const $elements = this instanceof $ ? this : $(elements);
  93. return $elements.each(function() {
  94. let data = $.data(this, 'bs.submenu');
  95. if (!data) {
  96. data = new DropdownSubmenu(this);
  97. $.data(this, 'bs.submenu', data);
  98. }
  99. });
  100. };
  101. return DropdownSubmenu;
  102. });