diff --git a/includes/SkinVector.php b/includes/SkinVector.php
index 5685caa..9aeb7ba 100644
--- a/includes/SkinVector.php
+++ b/includes/SkinVector.php
@@ -65,6 +65,7 @@ class SkinVector extends SkinMustache {
'is-quiet' => true,
'class' => 'sticky-header-icon'
];
+ private const SEARCH_EXPANDING_CLASS = 'vector-search-box-show-thumbnail';
/**
* T243281: Code used to track clicks to opt-out link.
@@ -325,7 +326,16 @@ class SkinVector extends SkinMustache {
private function getStickyHeaderData() {
return [
'data-primary-action' => !$this->shouldHideLanguages() ? $this->getULSButtonData() : '',
- 'data-button-start' => self::NO_ICON,
+ 'data-button-start' => [
+ 'href' => '#p-search',
+ 'label' => $this->msg( 'search' ),
+ 'icon' => 'wikimedia-search',
+ 'is-quiet' => true,
+ 'class' => 'vector-sticky-header-search-toggle',
+ ],
+ 'data-search' => [
+ 'class' => $this->shouldSearchExpand() ? self::SEARCH_EXPANDING_CLASS : '',
+ ],
'data-buttons' => [
self::TALK_ICON, self::HISTORY_ICON, self::NO_ICON, self::NO_ICON
]
@@ -423,7 +433,7 @@ class SkinVector extends SkinMustache {
}
if ( $this->shouldSearchExpand() ) {
- $searchClass .= ' vector-search-box-show-thumbnail';
+ $searchClass .= " " . self::SEARCH_EXPANDING_CLASS;
}
// Annotate search box with a component class.
diff --git a/includes/templates/StickyHeader.mustache b/includes/templates/StickyHeader.mustache
index fe531fc..616ba9f 100644
--- a/includes/templates/StickyHeader.mustache
+++ b/includes/templates/StickyHeader.mustache
@@ -6,6 +6,11 @@
{{>Button}}
{{/data-button-start}}
+ {{#data-search}}
+
+
+
+ {{/data-search}}
diff --git a/jsdoc.json b/jsdoc.json
index 28eedc2..9c3e5bf 100644
--- a/jsdoc.json
+++ b/jsdoc.json
@@ -22,6 +22,7 @@
"Element": "https://developer.mozilla.org/docs/Web/API/Element",
"Event": "https://developer.mozilla.org/docs/Web/API/Event",
"HTMLElement": "https://developer.mozilla.org/docs/Web/API/HTMLElement",
+ "NodeList": "https://developer.mozilla.org/docs/Web/API/NodeList",
"HTMLInputElement": "https://developer.mozilla.org/docs/Web/API/HTMLInputElement",
"\"removeEventListener\"": "https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener",
"Window": "https://developer.mozilla.org/docs/Web/API/Window",
diff --git a/resources/skins.vector.js/searchToggle.js b/resources/skins.vector.js/searchToggle.js
index 015a4c0..591156d 100644
--- a/resources/skins.vector.js/searchToggle.js
+++ b/resources/skins.vector.js/searchToggle.js
@@ -1,7 +1,6 @@
var
- HEADER_SELECTOR = '.mw-header',
- SEARCH_TOGGLE_SELECTOR = '.search-toggle',
- SEARCH_BOX_ID = 'p-search',
+ HEADER_SELECTOR = 'header',
+ SEARCH_BOX_SELECTOR = '.vector-search-box',
SEARCH_VISIBLE_CLASS = 'vector-header-search-toggled';
/**
@@ -22,7 +21,9 @@ function bindSearchBoxHandler( searchBox, header ) {
// 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.
- !$( ev.target ).closest( '.wvui-typeahead-suggestion' ).length &&
+ //
+ // 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
@@ -53,15 +54,20 @@ function bindToggleClickHandler( searchBox, header, searchToggle ) {
// from the page when clicked.
ev.preventDefault();
- bindSearchBoxHandler( searchBox, header );
-
// eslint-disable-next-line mediawiki/class-doc
header.classList.add( SEARCH_VISIBLE_CLASS );
- // Defer focusing the input to another task in the event loop. At the time
+ // 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 ) {
@@ -73,14 +79,34 @@ function bindToggleClickHandler( searchBox, header, searchToggle ) {
searchToggle.addEventListener( 'click', handler );
}
-module.exports = function initSearchToggle() {
- var
- header = /** @type {HTMLElement|null} */ ( document.querySelector( HEADER_SELECTOR ) ),
- searchBox = /** @type {HTMLElement|null} */ ( document.getElementById( SEARCH_BOX_ID ) ),
- searchToggle =
- /** @type {HTMLElement|null} */ ( document.querySelector( SEARCH_TOGGLE_SELECTOR ) );
+/**
+ * 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;
+ }
- if ( !( searchBox && searchToggle && header ) ) {
+ 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;
}
diff --git a/resources/skins.vector.js/skin.js b/resources/skins.vector.js/skin.js
index 27592db..431102b 100644
--- a/resources/skins.vector.js/skin.js
+++ b/resources/skins.vector.js/skin.js
@@ -50,7 +50,9 @@ function main( window ) {
dropdownMenus();
vector.init();
initSearchLoader( document );
- searchToggle();
+ // Initialize the search toggle for the main header only. The sticky header
+ // toggle is initialized after wvui search loads.
+ searchToggle( document.querySelector( '.mw-header .search-toggle' ) );
languageButton();
stickyHeader();
}
diff --git a/resources/skins.vector.js/stickyHeader.js b/resources/skins.vector.js/stickyHeader.js
index f9f9e79..bbb41fc 100644
--- a/resources/skins.vector.js/stickyHeader.js
+++ b/resources/skins.vector.js/stickyHeader.js
@@ -6,7 +6,8 @@ var
FIRST_HEADING_ID = 'firstHeading',
USER_MENU_ID = 'p-personal',
VECTOR_USER_LINKS_SELECTOR = '.vector-user-links',
- VECTOR_MENU_CONTENT_LIST_SELECTOR = '.vector-menu-content-list';
+ VECTOR_MENU_CONTENT_LIST_SELECTOR = '.vector-menu-content-list',
+ SEARCH_TOGGLE_SELECTOR = '.vector-sticky-header-search-toggle';
/**
* Copies attribute from an element to another.
@@ -141,6 +142,31 @@ function makeStickyHeaderFunctional(
stickyObserver.observe( stickyIntersection );
}
+/**
+ * @param {HTMLElement} header
+ */
+function setupSearchIfNeeded( header ) {
+ var
+ searchToggle = header.querySelector( SEARCH_TOGGLE_SELECTOR );
+
+ if ( !(
+ searchToggle &&
+ window.fetch &&
+ document.body.classList.contains( 'skin-vector-search-vue' )
+ ) ) {
+ return;
+ }
+
+ // Load the `skins.vector.search` module here or setup an event handler to
+ // load it depending on the outcome of T289718. After it loads, initialize the
+ // search toggle.
+ //
+ // Example:
+ // mw.loader.using( 'skins.vector.search', function () {
+ // initSearchToggle( searchToggle );
+ // } );
+}
+
module.exports = function initStickyHeader() {
var header = document.getElementById( STICKY_HEADER_ID ),
stickyIntersection = document.getElementById(
@@ -162,4 +188,5 @@ module.exports = function initStickyHeader() {
}
makeStickyHeaderFunctional( header, stickyIntersection, userMenu, userMenuStickyContainer );
+ setupSearchIfNeeded( header );
};
diff --git a/resources/skins.vector.search/App.vue b/resources/skins.vector.search/App.vue
index 48a48b9..61301fe 100644
--- a/resources/skins.vector.search/App.vue
+++ b/resources/skins.vector.search/App.vue
@@ -1,6 +1,6 @@
`
},
- 'data-button-start': NO_ICON,
+ 'data-search': {
+ class: ''
+ },
+ 'data-button-start': {
+ icon: 'wikimedia-search',
+ href: '#',
+ class: 'search-toggle',
+ 'is-quiet': true,
+ label: 'Search'
+ },
'data-button-end': NO_ICON,
'data-buttons': [
NO_ICON, NO_ICON, NO_ICON, NO_ICON