search.es6.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * @file
  3. * Wide viewport search bar interactions.
  4. */
  5. ((Drupal) => {
  6. const searchWideButtonSelector =
  7. '[data-drupal-selector="block-search-wide-button"]';
  8. const searchWideButton = document.querySelector(searchWideButtonSelector);
  9. const searchWideWrapperSelector =
  10. '[data-drupal-selector="block-search-wide-wrapper"]';
  11. const searchWideWrapper = document.querySelector(searchWideWrapperSelector);
  12. /**
  13. * Determine if search is visible.
  14. *
  15. * @return {boolean}
  16. * True if the search wrapper contains "is-active" class, false if not.
  17. */
  18. function searchIsVisible() {
  19. return searchWideWrapper.classList.contains('is-active');
  20. }
  21. Drupal.olivero.searchIsVisible = searchIsVisible;
  22. /**
  23. * Closes search bar when a click event does not happen at an (x,y) coordinate
  24. * that does not overlap with either the search wrapper or button.
  25. *
  26. * @see https://bugs.webkit.org/show_bug.cgi?id=229895
  27. *
  28. * @param {Event} e click event
  29. */
  30. function watchForClickOut(e) {
  31. const clickInSearchArea = e.target.matches(`
  32. ${searchWideWrapperSelector},
  33. ${searchWideWrapperSelector} *,
  34. ${searchWideButtonSelector},
  35. ${searchWideButtonSelector} *
  36. `);
  37. if (!clickInSearchArea && searchIsVisible()) {
  38. // eslint-disable-next-line no-use-before-define
  39. toggleSearchVisibility(false);
  40. }
  41. }
  42. /**
  43. * Closes search bar when focus moves to another target.
  44. * Avoids closing search bar if event does not have related target - required for Safari.
  45. *
  46. * @see https://bugs.webkit.org/show_bug.cgi?id=229895
  47. *
  48. * @param {Event} e focusout event
  49. */
  50. function watchForFocusOut(e) {
  51. if (e.relatedTarget) {
  52. const inSearchBar = e.relatedTarget.matches(
  53. `${searchWideWrapperSelector}, ${searchWideWrapperSelector} *`,
  54. );
  55. const inSearchButton = e.relatedTarget.matches(
  56. `${searchWideButtonSelector}, ${searchWideButtonSelector} *`,
  57. );
  58. if (!inSearchBar && !inSearchButton) {
  59. // eslint-disable-next-line no-use-before-define
  60. toggleSearchVisibility(false);
  61. }
  62. }
  63. }
  64. /**
  65. * Closes search bar on escape keyup, if open.
  66. *
  67. * @param {Event} e keyup event
  68. */
  69. function watchForEscapeOut(e) {
  70. if (e.key === 'Escape' || e.key === 'Esc') {
  71. // eslint-disable-next-line no-use-before-define
  72. toggleSearchVisibility(false);
  73. }
  74. }
  75. /**
  76. * Set focus for the search input element.
  77. */
  78. function handleFocus() {
  79. if (searchIsVisible()) {
  80. searchWideWrapper.querySelector('input[type="search"]').focus();
  81. } else if (searchWideWrapper.contains(document.activeElement)) {
  82. // Return focus to button only if focus was inside of the search wrapper.
  83. searchWideButton.focus();
  84. }
  85. }
  86. /**
  87. * Toggle search functionality visibility.
  88. *
  89. * @param {boolean} visibility
  90. * True if we want to show the form, false if we want to hide it.
  91. */
  92. function toggleSearchVisibility(visibility) {
  93. searchWideButton.setAttribute('aria-expanded', visibility === true);
  94. searchWideWrapper.addEventListener('transitionend', handleFocus, {
  95. once: true,
  96. });
  97. if (visibility === true) {
  98. Drupal.olivero.closeAllSubNav();
  99. searchWideWrapper.classList.add('is-active');
  100. document.addEventListener('click', watchForClickOut, { capture: true });
  101. document.addEventListener('focusout', watchForFocusOut, {
  102. capture: true,
  103. });
  104. document.addEventListener('keyup', watchForEscapeOut, { capture: true });
  105. } else {
  106. searchWideWrapper.classList.remove('is-active');
  107. document.removeEventListener('click', watchForClickOut, {
  108. capture: true,
  109. });
  110. document.removeEventListener('focusout', watchForFocusOut, {
  111. capture: true,
  112. });
  113. document.removeEventListener('keyup', watchForEscapeOut, {
  114. capture: true,
  115. });
  116. }
  117. }
  118. Drupal.olivero.toggleSearchVisibility = toggleSearchVisibility;
  119. /**
  120. * Initializes the search wide button.
  121. *
  122. * @type {Drupal~behavior}
  123. *
  124. * @prop {Drupal~behaviorAttach} attach
  125. * Adds aria-expanded attribute to the search wide button.
  126. */
  127. Drupal.behaviors.searchWide = {
  128. attach(context) {
  129. const searchWideButtonEl = once(
  130. 'search-wide',
  131. searchWideButtonSelector,
  132. context,
  133. ).shift();
  134. if (searchWideButtonEl) {
  135. searchWideButtonEl.setAttribute('aria-expanded', searchIsVisible());
  136. searchWideButtonEl.addEventListener('click', () => {
  137. toggleSearchVisibility(!searchIsVisible());
  138. });
  139. }
  140. },
  141. };
  142. })(Drupal);