mediawiki-skins-Vector/resources/skins.vector.es6/searchToggle.js
jdlrobson ca0401789d ES6-ify sticky header code
- Can now use const/let
- No need for feature detection for things like fetch and closest
as we can assume they exist if ES6 support is available

Change-Id: I85b01add13fd74e1514119498815403e42a09af0
2021-10-21 23:44:30 +00:00

110 lines
3.3 KiB
JavaScript

const
HEADER_SELECTOR = 'header',
SEARCH_BOX_SELECTOR = '.vector-search-box',
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
*/
const clickHandler = ( ev ) => {
if (
ev.target instanceof HTMLElement &&
// Check if the click target was a suggestion link. WVUI clears the
// suggestion elements from the DOM when a suggestion is clicked so we
// can't test if the suggestion is a child of the searchBox.
//
// Note: The .closest API is feature detected in `initSearchToggle`.
!ev.target.closest( '.wvui-typeahead-suggestion' ) &&
!searchBox.contains( ev.target )
) {
// eslint-disable-next-line mediawiki/class-doc
header.classList.remove( SEARCH_VISIBLE_CLASS );
document.removeEventListener( 'click', clickHandler );
}
};
document.addEventListener( 'click', clickHandler );
}
/**
* Binds event handlers necessary for the searchBox to show when the toggle is
* clicked.
*
* @param {HTMLElement} searchBox
* @param {HTMLElement} header
* @param {Element} searchToggle
*/
function bindToggleClickHandler( searchBox, header, searchToggle ) {
/**
* @param {Event} ev
* @ignore
*/
const handler = ( ev ) => {
// The toggle is an anchor element. Prevent the browser from navigating away
// from the page when clicked.
ev.preventDefault();
// eslint-disable-next-line mediawiki/class-doc
header.classList.add( SEARCH_VISIBLE_CLASS );
// 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
// of this writing, Safari 14.0.3 has trouble changing the visibility of the
// element and focusing the input within the same task.
setTimeout( () => {
bindSearchBoxHandler( searchBox, header );
const searchInput = /** @type {HTMLInputElement|null} */ ( searchBox.querySelector( 'input[type="search"]' ) );
if ( searchInput ) {
searchInput.focus();
}
} );
};
searchToggle.addEventListener( 'click', handler );
}
/**
* 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.
*
* @param {HTMLElement|Element} searchToggle
*/
module.exports = function initSearchToggle( searchToggle ) {
const header =
/** @type {HTMLElement|null} */ ( searchToggle.closest( HEADER_SELECTOR ) );
if ( !header ) {
return;
}
const searchBox =
/** @type {HTMLElement|null} */ ( header.querySelector( SEARCH_BOX_SELECTOR ) );
if ( !searchBox ) {
return;
}
bindToggleClickHandler( searchBox, header, searchToggle );
};