diff --git a/includes/SkinVector.php b/includes/SkinVector.php index e3ef812..c94c1f4 100644 --- a/includes/SkinVector.php +++ b/includes/SkinVector.php @@ -105,7 +105,8 @@ class SkinVector extends SkinMustache { 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; - private const SEARCH_EXPANDING_CLASS = 'vector-search-box-show-thumbnail'; + private const SEARCH_SHOW_THUMBNAIL_CLASS = 'vector-search-box-show-thumbnail'; + private const SEARCH_AUTO_EXPAND_WIDTH_CLASS = 'vector-search-box-auto-expand-width'; private const STICKY_HEADER_ENABLED_CLASS = 'vector-sticky-header-enabled'; private const CLASS_QUIET_BUTTON = 'mw-ui-button mw-ui-quiet'; private const CLASS_PROGRESSIVE = 'mw-ui-progressive'; @@ -583,7 +584,8 @@ class SkinVector extends SkinMustache { !$this->isLegacy(), // is primary mode of search true, - 'searchform' + 'searchform', + true ), 'data-vector-sticky-header' => VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER @@ -593,7 +595,8 @@ class SkinVector extends SkinMustache { // Collapse inside search box is disabled. false, false, - 'vector-sticky-search-form' + 'vector-sticky-search-form', + false ), VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER_EDIT @@ -657,9 +660,16 @@ class SkinVector extends SkinMustache { * @param bool $isCollapsible * @param bool $isPrimary * @param string $formId + * @param bool $autoExpandWidth * @return array modified version of $searchBoxData */ - private function getSearchData( array $searchBoxData, bool $isCollapsible, bool $isPrimary, string $formId ) { + private function getSearchData( + array $searchBoxData, + bool $isCollapsible, + bool $isPrimary, + string $formId, + bool $autoExpandWidth + ) { $searchClass = ''; // Determine the search widget treatment to send to the user @@ -671,8 +681,9 @@ class SkinVector extends SkinMustache { $searchClass .= ' vector-search-box-collapses '; } - if ( $this->shouldSearchExpand() ) { - $searchClass .= ' ' . self::SEARCH_EXPANDING_CLASS; + if ( $this->doesSearchHaveThumbnails() ) { + $searchClass .= ' ' . self::SEARCH_SHOW_THUMBNAIL_CLASS . + ( $autoExpandWidth ? ' ' . self::SEARCH_AUTO_EXPAND_WIDTH_CLASS : '' ); } // Annotate search box with a component class. @@ -728,15 +739,12 @@ class SkinVector extends SkinMustache { } /** - * Determines whether or not the search input should expand when focused - * before WVUI search is loaded. In WVUI, the search input expands to - * accomodate thumbnails in the suggestion list. When thumbnails are - * disabled, the input should not expand. Note this is only relevant for WVUI - * search (not legacy search). + * Returns `true` if WVUI is enabled to show thumbnails and `false` otherwise. + * Note this is only relevant for WVUI search (not legacy search). * * @return bool */ - private function shouldSearchExpand(): bool { + private function doesSearchHaveThumbnails(): bool { $featureManager = VectorServices::getFeatureManager(); return $featureManager->isFeatureEnabled( Constants::FEATURE_USE_WVUI_SEARCH ) && diff --git a/resources/skins.vector.search/App.vue b/resources/skins.vector.search/App.vue index cbd3af2..2c22386 100644 --- a/resources/skins.vector.search/App.vue +++ b/resources/skins.vector.search/App.vue @@ -17,6 +17,7 @@ :show-thumbnail="showThumbnail" :show-description="showDescription" :highlight-query="highlightQuery" + :auto-expand-width="autoExpandWidth" @fetch-start="instrumentation.onFetchStart" @fetch-end="instrumentation.onFetchEnd" @suggestion-click="instrumentation.onSuggestionClick" @@ -121,6 +122,10 @@ module.exports = { highlightQuery: { type: Boolean, default: true + }, + autoExpandWidth: { + type: Boolean, + default: false } }, data: function () { diff --git a/resources/skins.vector.search/skins.vector.search.js b/resources/skins.vector.search/skins.vector.search.js index c337007..e51ea7c 100644 --- a/resources/skins.vector.search/skins.vector.search.js +++ b/resources/skins.vector.search/skins.vector.search.js @@ -6,18 +6,18 @@ var config = require( './config.json' ); /** - * @param {Element} searchForm + * @param {Element} searchBox * @return {void} */ -function initApp( searchForm ) { - var +function initApp( searchBox ) { + var searchForm = searchBox.querySelector( '.vector-search-box-form' ), titleInput = /** @type {HTMLInputElement|null} */ ( - searchForm.querySelector( 'input[name=title]' ) + searchBox.querySelector( 'input[name=title]' ) ), - search = /** @type {HTMLInputElement|null} */ ( searchForm.querySelector( 'input[name="search"]' ) ), + search = /** @type {HTMLInputElement|null} */ ( searchBox.querySelector( 'input[name="search"]' ) ), searchPageTitle = titleInput && titleInput.value; - if ( !search || !titleInput ) { + if ( !searchForm || !search || !titleInput ) { throw new Error( 'Attempted to create Vue search element from an incompatible element.' ); } @@ -31,7 +31,8 @@ function initApp( searchForm ) { searchPageTitle: searchPageTitle, searchTitle: search.getAttribute( 'title' ), searchPlaceholder: search.getAttribute( 'placeholder' ), - searchQuery: search.value + searchQuery: search.value, + autoExpandWidth: searchBox ? searchBox.classList.contains( 'vector-search-box-auto-expand-width' ) : false // Pass additional config from server. }, config ) ) @@ -42,11 +43,10 @@ function initApp( searchForm ) { * @return {void} */ function main( document ) { - var - searchForms = document.querySelectorAll( '.vector-search-box-form' ); + var searchBoxes = document.querySelectorAll( '.vector-search-box' ); - searchForms.forEach( function ( searchForm ) { - initApp( searchForm ); + searchBoxes.forEach( function ( searchBox ) { + initApp( searchBox ); } ); } main( document ); diff --git a/resources/skins.vector.styles/components/Header.less b/resources/skins.vector.styles/components/Header.less index 5c0b535..f345d72 100644 --- a/resources/skins.vector.styles/components/Header.less +++ b/resources/skins.vector.styles/components/Header.less @@ -32,9 +32,20 @@ flex-grow: 1; } + // Allocate space for the extra width of the input when the search component + // has thumbnails. + // + // FIXME: This can be removed when WVUI in core has been upgraded to use the + // `.wvui-typeahead-search--auto-expand-width` class. + .wvui-typeahead-search--show-thumbnail:not( .wvui-typeahead-search--auto-expand-width ) { + margin-left: @size-search-expand; + } + @media ( min-width: @width-breakpoint-desktop ) { + @margin-start-search: 40px; + .vector-search-box { - margin-left: unit( 40px / @font-size-browser / @font-size-base, em ); // 2.85714286em @ 16 & 0.875em + margin-left: unit( @margin-start-search / @font-size-browser / @font-size-base, em ); // 2.85714286em @ 16 & 0.875em margin-right: @margin-end-search; // Support: IE 8, Firefox 18-, Chrome 19-, Safari 5.1-, Opera 19-, Android 4.4.4-. @@ -45,10 +56,17 @@ min-width: @min-width-search-desktop; flex-basis: @min-width-search; - &-form, - .wvui-typeahead-search { + > div { max-width: @max-width-search; } + + &.vector-search-box-show-thumbnail { + margin-left: unit( ( @margin-start-search - @size-search-expand ) / @font-size-browser / @font-size-base, em ); // 1.14285714em @ 16 & 0.875em + + > div { + max-width: @max-width-search + unit( @size-search-expand / @font-size-browser / @font-size-base, em ); // 37.42857143em @ 16 & 0.875em + } + } } } @@ -75,15 +93,19 @@ margin-right: @margin-end-search; } - // Increase the start margin of the search box to account for the input - // expanding on focus. - .vector-search-box-show-thumbnail { - margin-left: @size-search-expand + @padding-horizontal-tabs; - } - .wvui-typeahead-search__wrapper { position: static; } + + // Position the start of suggestion list at the start of the input. + // + // FIXME: This can be removed when WVUI in core has been upgraded to use + // the `.wvui-typeahead-search--auto-expand-width` class. + .wvui-typeahead-search--show-thumbnail:not( .wvui-typeahead-search--auto-expand-width ) { + .wvui-typeahead-search__suggestions { + left: 0; + } + } } } } diff --git a/resources/skins.vector.styles/components/StickyHeader.less b/resources/skins.vector.styles/components/StickyHeader.less index f250eb2..d77f5aa 100644 --- a/resources/skins.vector.styles/components/StickyHeader.less +++ b/resources/skins.vector.styles/components/StickyHeader.less @@ -101,6 +101,11 @@ } &.vector-header-search-toggled { + // .wvui-input__input left padding (36px) - the .wvui-icon svg width (20px) + // - the icon left padding (12px [1]) = 4px + // [1] see .wvui-typeahead-search--show-thumbnail .wvui-input__input:focus) + @margin-start-search-box: 4px; + .vector-sticky-header-search-toggle, .vector-sticky-header-context-bar { display: none; @@ -109,24 +114,27 @@ .vector-search-box { display: block; flex-basis: unit( 500px / @font-size-browser / @font-size-base, em ); - - .wvui-typeahead-search { - // .wvui-input__input left padding (36px) - the .wvui-icon svg width (20px) - // - the icon left padding (12px [1]) = 4px - // [1] see .wvui-typeahead-search--show-thumbnail .wvui-input__input:focus) - margin-left: 4px; - } + margin-left: @margin-start-search-box; } - // T296318 Increase the start margin of the search box to account for the input - // expanding on focus. + // T296318 Decrease the start margin of the search box to account for the + // icon's increased start position when the search component has thumbnails. .vector-search-box-show-thumbnail { - margin-left: @margin-end-search; + margin-left: @margin-start-search-box - ( @size-search-expand / 2 ); .wvui-input__start-icon { color: @colorGray2; } } + + // Allocate space for the extra width of the input when the search component + // has thumbnails. + // + // FIXME: This can be removed when WVUI in core has been upgraded + // to use the `.wvui-typeahead-search--full-width` class. + .wvui-typeahead-search--show-thumbnail:not( .wvui-typeahead-search--full-width ) { + margin-left: @size-search-expand; + } } } diff --git a/resources/skins.vector.styles/components/VueEnhancedSearchBox.less b/resources/skins.vector.styles/components/VueEnhancedSearchBox.less index 286c22e..19ee08a 100644 --- a/resources/skins.vector.styles/components/VueEnhancedSearchBox.less +++ b/resources/skins.vector.styles/components/VueEnhancedSearchBox.less @@ -81,7 +81,7 @@ bottom: 0; // Accounts for the 1px input border. Derived from // https://gerrit.wikimedia.org/g/wvui/+/refs/changes/93/650593/10/src/components/input/Input.vue#163 - left: 1px; + left: @border-width-base; // Increase size to match WVUI. width: @size-search-figure; } @@ -93,13 +93,23 @@ } .vector-search-box-show-thumbnail { + .searchButton { + // Accounts for the margin that allocates space for the input expanding and + // 1px input border. + left: @size-search-expand + @border-width-base; + } + + .vector-search-box-input { + margin-left: @size-search-expand; + width: ~'calc( 100% - @{size-search-expand} )'; + } + // Recreate WVUI expanding input. .vector-search-box-input:focus { - position: relative; + margin-left: 0; // Use ~ and fixed values to disable the LESS transformation in ResourceLoader LESS implementation. padding-left: ~'calc( @{size-search-figure} + @{size-search-expand} )'; - width: ~'calc( 100% + @{size-search-expand} )'; - left: ~'calc( -1 * @{size-search-expand} )'; + width: 100%; } // Reposition search icon for expanded input. @@ -107,15 +117,8 @@ // Derived from // https://gerrit.wikimedia.org/g/wvui/+/refs/changes/93/650593/10/src/components/typeahead-search/TypeaheadSearch.vue#655 // (12px of space between the border and the icon) with 1px to account for the focused input border. - @space-icon-start: 12px; - left: -@size-search-expand + @space-icon-start + @border-width-base; - } - - // Update search loader to match width and position of WVUI expanding input. - .vector-search-box-inner.search-form__loader:after { - width: ~'calc( 100% + @{size-search-expand} )'; - left: ~'calc( -1 * @{size-search-expand} )'; - padding-left: @size-search-expand; + @space-icon-start: @size-search-expand / 2; + left: @space-icon-start + @border-width-base; } } }