2021-10-20 19:10:42 +00:00
|
|
|
const
|
2021-09-09 21:40:06 +00:00
|
|
|
HEADER_SELECTOR = 'header',
|
|
|
|
SEARCH_BOX_SELECTOR = '.vector-search-box',
|
2021-06-30 07:36:23 +00:00
|
|
|
SEARCH_VISIBLE_CLASS = 'vector-header-search-toggled';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds event handlers necessary for the searchBox to disappear when the user
|
|
|
|
* clicks outside the searchBox.
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} searchBox
|
|
|
|
* @param {HTMLElement} header
|
|
|
|
*/
|
|
|
|
function bindSearchBoxHandler( searchBox, header ) {
|
|
|
|
/**
|
|
|
|
* @param {Event} ev
|
|
|
|
* @ignore
|
|
|
|
*/
|
2021-10-20 19:10:42 +00:00
|
|
|
const clickHandler = ( ev ) => {
|
2021-06-30 07:36:23 +00:00
|
|
|
if (
|
|
|
|
ev.target instanceof HTMLElement &&
|
2022-02-01 20:52:16 +00:00
|
|
|
// Check if the click target was a suggestion link. Codex clears the
|
2021-06-30 07:36:23 +00:00
|
|
|
// suggestion elements from the DOM when a suggestion is clicked so we
|
|
|
|
// can't test if the suggestion is a child of the searchBox.
|
2021-09-09 21:40:06 +00:00
|
|
|
//
|
|
|
|
// Note: The .closest API is feature detected in `initSearchToggle`.
|
2022-02-01 20:52:16 +00:00
|
|
|
!ev.target.closest( '.cdx-typeahead-search .cdx-menu-item__content' ) &&
|
2021-06-30 07:36:23 +00:00
|
|
|
!searchBox.contains( ev.target )
|
|
|
|
) {
|
|
|
|
header.classList.remove( SEARCH_VISIBLE_CLASS );
|
|
|
|
|
|
|
|
document.removeEventListener( 'click', clickHandler );
|
|
|
|
}
|
2021-10-20 19:10:42 +00:00
|
|
|
};
|
2021-06-30 07:36:23 +00:00
|
|
|
|
|
|
|
document.addEventListener( 'click', clickHandler );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds event handlers necessary for the searchBox to show when the toggle is
|
|
|
|
* clicked.
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} searchBox
|
|
|
|
* @param {HTMLElement} header
|
2021-09-16 18:00:05 +00:00
|
|
|
* @param {Element} searchToggle
|
2021-06-30 07:36:23 +00:00
|
|
|
*/
|
|
|
|
function bindToggleClickHandler( searchBox, header, searchToggle ) {
|
|
|
|
/**
|
|
|
|
* @param {Event} ev
|
|
|
|
* @ignore
|
|
|
|
*/
|
2021-10-20 19:10:42 +00:00
|
|
|
const handler = ( ev ) => {
|
2021-06-30 07:36:23 +00:00
|
|
|
// The toggle is an anchor element. Prevent the browser from navigating away
|
|
|
|
// from the page when clicked.
|
|
|
|
ev.preventDefault();
|
|
|
|
|
|
|
|
header.classList.add( SEARCH_VISIBLE_CLASS );
|
|
|
|
|
2021-09-09 21:40:06 +00:00
|
|
|
// Defer binding the search box handler until after the event bubbles to the
|
|
|
|
// top of the document so that the handler isn't called when the user clicks
|
|
|
|
// the search toggle. Event bubbled callbacks execute within the same task
|
|
|
|
// in the event loop.
|
|
|
|
//
|
|
|
|
// Also, defer focusing the input to another task in the event loop. At the time
|
2021-06-30 07:36:23 +00:00
|
|
|
// of this writing, Safari 14.0.3 has trouble changing the visibility of the
|
|
|
|
// element and focusing the input within the same task.
|
2021-10-20 19:10:42 +00:00
|
|
|
setTimeout( () => {
|
2021-09-09 21:40:06 +00:00
|
|
|
bindSearchBoxHandler( searchBox, header );
|
|
|
|
|
2021-10-20 19:10:42 +00:00
|
|
|
const searchInput = /** @type {HTMLInputElement|null} */ ( searchBox.querySelector( 'input[type="search"]' ) );
|
2021-06-30 07:36:23 +00:00
|
|
|
|
|
|
|
if ( searchInput ) {
|
2021-12-14 17:53:50 +00:00
|
|
|
const beforeScrollX = window.scrollX;
|
|
|
|
const beforeScrollY = window.scrollY;
|
2021-06-30 07:36:23 +00:00
|
|
|
searchInput.focus();
|
2021-12-14 17:53:50 +00:00
|
|
|
// For some reason, Safari 14,15 tends to undesirably change the scroll
|
|
|
|
// position of `input` elements inside fixed position elements.
|
|
|
|
// While an Internet search suggests similar problems with mobile Safari
|
|
|
|
// it didn't yield any results for desktop Safari.
|
|
|
|
// This line resets any unexpected scrolling that occurred while the
|
|
|
|
// input received focus.
|
|
|
|
// If you are in the future with a modern version of Safari, where 14 and 15
|
|
|
|
// receive a low amount of page views, please reference T297636 and test
|
|
|
|
// to see whether this line of code can be removed.
|
|
|
|
// Additionally, these lines might become unnecessary when/if Safari
|
|
|
|
// supports the `preventScroll` focus option [1] in the future:
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#parameters
|
|
|
|
if ( beforeScrollX !== undefined && beforeScrollY !== undefined ) {
|
|
|
|
window.scroll( beforeScrollX, beforeScrollY );
|
|
|
|
}
|
2021-06-30 07:36:23 +00:00
|
|
|
}
|
|
|
|
} );
|
2021-10-20 19:10:42 +00:00
|
|
|
};
|
2021-06-30 07:36:23 +00:00
|
|
|
|
|
|
|
searchToggle.addEventListener( 'click', handler );
|
|
|
|
}
|
|
|
|
|
2021-09-09 21:40:06 +00:00
|
|
|
/**
|
|
|
|
* Enables search toggling behavior in a header given a toggle element (e.g.
|
|
|
|
* search icon). When the toggle element is clicked, a class,
|
|
|
|
* `SEARCH_VISIBLE_CLASS`, will be applied to a header matching the selector
|
|
|
|
* `HEADER_SELECTOR` and the input inside the element, SEARCH_BOX_SELECTOR, will
|
|
|
|
* be focused. This class can be used in CSS to show/hide the necessary
|
|
|
|
* elements. When the user clicks outside of SEARCH_BOX_SELECTOR, the class will
|
|
|
|
* be removed.
|
|
|
|
*
|
2021-10-20 19:10:42 +00:00
|
|
|
* @param {HTMLElement|Element} searchToggle
|
2021-09-09 21:40:06 +00:00
|
|
|
*/
|
|
|
|
module.exports = function initSearchToggle( searchToggle ) {
|
2021-10-20 19:10:42 +00:00
|
|
|
const header =
|
|
|
|
/** @type {HTMLElement|null} */ ( searchToggle.closest( HEADER_SELECTOR ) );
|
2021-09-09 21:40:06 +00:00
|
|
|
|
|
|
|
if ( !header ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-20 19:10:42 +00:00
|
|
|
const searchBox =
|
2021-09-09 21:40:06 +00:00
|
|
|
/** @type {HTMLElement|null} */ ( header.querySelector( SEARCH_BOX_SELECTOR ) );
|
2021-06-30 07:36:23 +00:00
|
|
|
|
2021-09-09 21:40:06 +00:00
|
|
|
if ( !searchBox ) {
|
2021-06-30 07:36:23 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bindToggleClickHandler( searchBox, header, searchToggle );
|
|
|
|
};
|