mediawiki-skins-Vector/resources/skins.vector.js/searchToggle.js
Nicholas Ray 93745e4800 Add search to sticky header
Per T289724#7342741, server renders an anchor tag pointing to #p-search
into the "button-start" bucket of the sticky header.

In the future after T289718, this anchor will then acts as a button when
the search module is loaded and searchToggle executes.

* skins.vector.search was modified to accomodate instantiating multiple
search components (one in the main header and one in the sticky
header).

* searchToggle.js was modified to accept a searchToggle element as a
param which the caller can then instantiate when ideal. For the sticky
header toggle, this needs to happen *after* the search module loads.
Before then, the toggle will act as a link.

* Drops one jQuery usage from searchToggle so that it can be jQuery
free. Because the native .closest method is used, IE11 support is also
dropped. However, the script feature detects and returns early if the
API isn't available.

* Makes App.vue accept an `id` prop so that multiple instances of it can
be created.

Bug: T289724
Change-Id: I1c5e6eee75918a0d06562d07c31fdcbd5a4ed6d5
2021-09-14 16:58:07 -07:00

115 lines
3.4 KiB
JavaScript

var
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
*/
function 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 {HTMLElement} searchToggle
*/
function bindToggleClickHandler( searchBox, header, searchToggle ) {
/**
* @param {Event} ev
* @ignore
*/
function 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( function () {
bindSearchBoxHandler( searchBox, header );
var 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|null} searchToggle
*/
module.exports = function initSearchToggle( searchToggle ) {
// Check if .closest API is available (IE11 does not support it).
if ( !searchToggle || !searchToggle.closest ) {
return;
}
var header =
/** @type {HTMLElement|null} */ ( searchToggle.closest( HEADER_SELECTOR ) );
if ( !header ) {
return;
}
var searchBox =
/** @type {HTMLElement|null} */ ( header.querySelector( SEARCH_BOX_SELECTOR ) );
if ( !searchBox ) {
return;
}
bindToggleClickHandler( searchBox, header, searchToggle );
};