diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index e5d20bb7b1edd5..d4835f39dec222 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -726,7 +726,7 @@ Help visitors find your content. ([Source](https://github.com/WordPress/gutenber - **Name:** core/search - **Category:** widgets - **Supports:** align (center, left, right), anchor, color (background, gradients, text), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** buttonPosition, buttonText, buttonUseIcon, label, placeholder, query, showLabel, width, widthUnit +- **Attributes:** buttonBehavior, buttonPosition, buttonText, buttonUseIcon, isSearchFieldHidden, label, placeholder, query, showLabel, width, widthUnit ## Separator diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json index 387295ebb36dea..c3e63556c3ff42 100644 --- a/packages/block-library/src/search/block.json +++ b/packages/block-library/src/search/block.json @@ -42,6 +42,14 @@ "query": { "type": "object", "default": {} + }, + "buttonBehavior": { + "type": "string", + "default": "expand-searchfield" + }, + "isSearchFieldHidden": { + "type": "boolean", + "default": false } }, "supports": { diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 5d82ced145926f..d74e03d216660f 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -19,7 +19,7 @@ import { useSetting, } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { ToolbarDropdownMenu, ToolbarGroup, @@ -59,6 +59,8 @@ import { // button is placed inside wrapper. const DEFAULT_INNER_PADDING = '4px'; +const BUTTON_BEHAVIOR_EXPAND = 'expand-searchfield'; + export default function SearchEdit( { className, attributes, @@ -77,6 +79,8 @@ export default function SearchEdit( { buttonText, buttonPosition, buttonUseIcon, + buttonBehavior, + isSearchFieldHidden, style, } = attributes; @@ -130,12 +134,33 @@ export default function SearchEdit( { const isButtonPositionOutside = 'button-outside' === buttonPosition; const hasNoButton = 'no-button' === buttonPosition; const hasOnlyButton = 'button-only' === buttonPosition; + const searchFieldRef = useRef(); + const buttonRef = useRef(); const units = useCustomUnits( { availableUnits: [ '%', 'px' ], defaultValues: { '%': PC_WIDTH_DEFAULT, px: PX_WIDTH_DEFAULT }, } ); + useEffect( () => { + if ( hasOnlyButton && ! isSelected ) { + setAttributes( { + isSearchFieldHidden: true, + } ); + } + }, [ hasOnlyButton, isSelected, setAttributes ] ); + + // Show the search field when width changes. + useEffect( () => { + if ( ! hasOnlyButton || ! isSelected ) { + return; + } + + setAttributes( { + isSearchFieldHidden: false, + } ); + }, [ hasOnlyButton, isSelected, setAttributes, width ] ); + const getBlockClassNames = () => { return classnames( className, @@ -152,6 +177,12 @@ export default function SearchEdit( { : undefined, buttonUseIcon && ! hasNoButton ? 'wp-block-search__icon-button' + : undefined, + hasOnlyButton && BUTTON_BEHAVIOR_EXPAND === buttonBehavior + ? 'wp-block-search__button-behavior-expand' + : undefined, + hasOnlyButton && isSearchFieldHidden + ? 'wp-block-search__searchfield-hidden' : undefined ); }; @@ -165,6 +196,7 @@ export default function SearchEdit( { onClick: () => { setAttributes( { buttonPosition: 'button-outside', + isSearchFieldHidden: false, } ); }, }, @@ -176,6 +208,7 @@ export default function SearchEdit( { onClick: () => { setAttributes( { buttonPosition: 'button-inside', + isSearchFieldHidden: false, } ); }, }, @@ -187,6 +220,19 @@ export default function SearchEdit( { onClick: () => { setAttributes( { buttonPosition: 'no-button', + isSearchFieldHidden: false, + } ); + }, + }, + { + role: 'menuitemradio', + title: __( 'Button Only' ), + isActive: buttonPosition === 'button-only', + icon: buttonOnly, + onClick: () => { + setAttributes( { + buttonPosition: 'button-only', + isSearchFieldHidden: true, } ); }, }, @@ -247,6 +293,7 @@ export default function SearchEdit( { onChange={ ( event ) => setAttributes( { placeholder: event.target.value } ) } + ref={ searchFieldRef } /> ); }; @@ -268,6 +315,13 @@ export default function SearchEdit( { ? { borderRadius } : borderProps.style ), }; + const handleButtonClick = () => { + if ( hasOnlyButton && BUTTON_BEHAVIOR_EXPAND === buttonBehavior ) { + setAttributes( { + isSearchFieldHidden: ! isSearchFieldHidden, + } ); + } + }; return ( <> @@ -281,6 +335,8 @@ export default function SearchEdit( { ? stripHTML( buttonText ) : __( 'Search' ) } + onClick={ handleButtonClick } + ref={ buttonRef } > @@ -297,6 +353,7 @@ export default function SearchEdit( { onChange={ ( html ) => setAttributes( { buttonText: html } ) } + onClick={ handleButtonClick } /> ) } @@ -516,14 +573,15 @@ export default function SearchEdit( { } } showHandle={ isSelected } > - { ( isButtonPositionInside || isButtonPositionOutside ) && ( + { ( isButtonPositionInside || + isButtonPositionOutside || + hasOnlyButton ) && ( <> { renderTextField() } { renderButton() } ) } - { hasOnlyButton && renderButton() } { hasNoButton && renderTextField() } diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index f42622551fc562..d125bacd1b51d8 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -29,11 +29,14 @@ function render_block_core_search( $attributes ) { $classnames = classnames_for_block_core_search( $attributes ); $show_label = ( ! empty( $attributes['showLabel'] ) ) ? true : false; $use_icon_button = ( ! empty( $attributes['buttonUseIcon'] ) ) ? true : false; - $show_input = ( ! empty( $attributes['buttonPosition'] ) && 'button-only' === $attributes['buttonPosition'] ) ? false : true; $show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true; + $button_position = $show_button ? $attributes['buttonPosition'] : null; $query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array(); + $button_behavior = ( ! empty( $attributes['buttonBehavior'] ) ) ? $attributes['buttonBehavior'] : 'default'; $input_markup = ''; $button_markup = ''; + $input_aria = ''; + $button_aria = ''; $query_params_markup = ''; $inline_styles = styles_for_block_core_search( $attributes ); $color_classes = get_color_classes_for_block_core_search( $attributes ); @@ -64,24 +67,28 @@ function render_block_core_search( $attributes ) { ); } - if ( $show_input ) { - $input_classes = array( 'wp-block-search__input' ); - if ( ! $is_button_inside && ! empty( $border_color_classes ) ) { - $input_classes[] = $border_color_classes; - } - if ( ! empty( $typography_classes ) ) { - $input_classes[] = $typography_classes; - } - $input_markup = sprintf( - '', - $input_id, - esc_attr( implode( ' ', $input_classes ) ), - get_search_query(), - esc_attr( $attributes['placeholder'] ), - $inline_styles['input'] - ); + if ( 'button-only' === $button_position && 'expand-searchfield' === $button_behavior ) { + $input_aria = 'aria-hidden="true" tabindex="-1"'; + wp_enqueue_script( 'wp-block--search-view', plugins_url( 'search/view.min.js', __FILE__ ) ); } + $input_classes = array( 'wp-block-search__input' ); + if ( ! $is_button_inside && ! empty( $border_color_classes ) ) { + $input_classes[] = $border_color_classes; + } + if ( ! empty( $typography_classes ) ) { + $input_classes[] = $typography_classes; + } + $input_markup = sprintf( + '', + $input_id, + esc_attr( implode( ' ', $input_classes ) ), + get_search_query(), + esc_attr( $attributes['placeholder'] ), + $inline_styles['input'], + $input_aria + ); + if ( count( $query_params ) > 0 ) { foreach ( $query_params as $param => $value ) { $query_params_markup .= sprintf( @@ -101,7 +108,6 @@ function render_block_core_search( $attributes ) { if ( ! empty( $typography_classes ) ) { $button_classes[] = $typography_classes; } - $aria_label = ''; if ( ! $is_button_inside && ! empty( $border_color_classes ) ) { $button_classes[] = $border_color_classes; @@ -111,7 +117,7 @@ function render_block_core_search( $attributes ) { $button_internal_markup = wp_kses_post( $attributes['buttonText'] ); } } else { - $aria_label = sprintf( 'aria-label="%s"', esc_attr( wp_strip_all_tags( $attributes['buttonText'] ) ) ); + $button_aria = sprintf( 'aria-label="%s"', esc_attr( wp_strip_all_tags( $attributes['buttonText'] ) ) ); $button_classes[] = 'has-icon'; $button_internal_markup = @@ -119,6 +125,9 @@ function render_block_core_search( $attributes ) { '; } + if ( 'expand-searchfield' === $attributes['buttonBehavior'] ) { + $button_aria = sprintf( 'aria-label="%s" aria-expanded="false" aria-controls="wp-block-search__input-%s"', __( 'Expand search field' ), esc_attr( $input_id ) ); + } // Include the button element class. $button_classes[] = wp_theme_get_element_class_name( 'button' ); @@ -126,7 +135,7 @@ function render_block_core_search( $attributes ) { '', esc_attr( implode( ' ', $button_classes ) ), $inline_styles['button'], - $aria_label, + $button_aria, $button_internal_markup ); } @@ -188,6 +197,9 @@ function classnames_for_block_core_search( $attributes ) { if ( 'button-only' === $attributes['buttonPosition'] ) { $classnames[] = 'wp-block-search__button-only'; + if ( ! empty( $attributes['buttonBehavior'] ) && 'expand-searchfield' === $attributes['buttonBehavior'] ) { + $classnames[] = 'wp-block-search__button-behavior-expand wp-block-search__searchfield-hidden'; + } } } @@ -294,10 +306,9 @@ function styles_for_block_core_search( $attributes ) { $show_label = ( isset( $attributes['showLabel'] ) ) && false !== $attributes['showLabel']; // Add width styles. - $has_width = ! empty( $attributes['width'] ) && ! empty( $attributes['widthUnit'] ); - $button_only = ! empty( $attributes['buttonPosition'] ) && 'button-only' === $attributes['buttonPosition']; + $has_width = ! empty( $attributes['width'] ) && ! empty( $attributes['widthUnit'] ); - if ( $has_width && ! $button_only ) { + if ( $has_width ) { $wrapper_styles[] = sprintf( 'width: %d%s;', esc_attr( $attributes['width'] ), diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss index 6f6c550dacbc15..1e813d9cf6f16a 100644 --- a/packages/block-library/src/search/style.scss +++ b/packages/block-library/src/search/style.scss @@ -81,3 +81,42 @@ $button-spacing-y: math.div($grid-unit-15, 2); // 6px .wp-block-search.aligncenter .wp-block-search__inside-wrapper { margin: auto; } + +.wp-block-search__button-behavior-expand { + .wp-block-search__inside-wrapper { + transition-property: width; + min-width: 0 !important; + } + + .wp-block-search__input { + transition-duration: 300ms; + flex-basis: 100%; + } + + // !important here to override inline styles on button only deselected view. + &.wp-block-search__searchfield-hidden { + overflow: hidden; + + .wp-block-search__inside-wrapper { + overflow: hidden; + } + + .wp-block-search__input { + width: 0 !important; + min-width: 0 !important; + padding-left: 0 !important; + padding-right: 0 !important; + border-left-width: 0 !important; + border-right-width: 0 !important; + flex-grow: 0; + margin: 0; + flex-basis: 0; + } + } +} + +.wp-block[data-align="right"] .wp-block-search__button-behavior-expand { + .wp-block-search__inside-wrapper { + float: right; + } +} diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js new file mode 100644 index 00000000000000..6af84de23edcfa --- /dev/null +++ b/packages/block-library/src/search/view.js @@ -0,0 +1,66 @@ +window.addEventListener( 'DOMContentLoaded', () => { + const hiddenClass = 'wp-block-search__searchfield-hidden'; + + Array.from( + document.getElementsByClassName( + 'wp-block-search__button-behavior-expand' + ) + ).forEach( ( block ) => { + const searchField = block.querySelector( '.wp-block-search__input' ); + const searchButton = block.querySelector( '.wp-block-search__button' ); + const searchLabel = block.querySelector( '.wp-block-search__label' ); + const ariaLabel = searchButton.getAttribute( 'aria-label' ); + const id = searchField.getAttribute( 'id' ); + + const toggleSearchField = ( showSearchField ) => { + if ( showSearchField ) { + searchField.removeAttribute( 'aria-hidden' ); + searchField.removeAttribute( 'tabindex' ); + searchButton.removeAttribute( 'aria-expanded' ); + searchButton.removeAttribute( 'aria-controls' ); + searchButton.setAttribute( 'type', 'submit' ); + searchButton.setAttribute( 'aria-label', 'Submit Search' ); + + return block.classList.remove( hiddenClass ); + } + + searchButton.removeAttribute( 'type' ); + searchField.setAttribute( 'aria-hidden', 'true' ); + searchField.setAttribute( 'tabindex', '-1' ); + searchButton.setAttribute( 'aria-expanded', 'false' ); + searchButton.setAttribute( 'aria-controls', id ); + searchButton.setAttribute( 'aria-label', ariaLabel ); + return block.classList.add( hiddenClass ); + }; + + const hideSearchField = ( e ) => { + if ( ! e.target.closest( '.wp-block-search' ) ) { + return toggleSearchField( false ); + } + + if ( e.key === 'Escape' ) { + searchButton.focus(); + return toggleSearchField( false ); + } + }; + + const handleButtonClick = ( e ) => { + if ( block.classList.contains( hiddenClass ) ) { + e.preventDefault(); + searchField.focus(); + toggleSearchField( true ); + } + }; + + searchButton.removeAttribute( 'type' ); + searchField.addEventListener( 'keydown', ( e ) => { + hideSearchField( e ); + } ); + searchButton.addEventListener( 'click', handleButtonClick ); + searchButton.addEventListener( 'keydown', ( e ) => { + hideSearchField( e ); + } ); + searchLabel.addEventListener( 'click', handleButtonClick ); + document.body.addEventListener( 'click', hideSearchField ); + } ); +} ); diff --git a/test/integration/fixtures/blocks/core__search.json b/test/integration/fixtures/blocks/core__search.json index 7b6125b5016b37..f692eac10993d8 100644 --- a/test/integration/fixtures/blocks/core__search.json +++ b/test/integration/fixtures/blocks/core__search.json @@ -7,7 +7,9 @@ "placeholder": "", "buttonPosition": "button-outside", "buttonUseIcon": false, - "query": {} + "query": {}, + "buttonBehavior": "expand-searchfield", + "isSearchFieldHidden": false }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__search__custom-text.json b/test/integration/fixtures/blocks/core__search__custom-text.json index 6e874946117966..c763cb60f65e86 100644 --- a/test/integration/fixtures/blocks/core__search__custom-text.json +++ b/test/integration/fixtures/blocks/core__search__custom-text.json @@ -9,7 +9,9 @@ "buttonText": "Custom button text", "buttonPosition": "button-outside", "buttonUseIcon": false, - "query": {} + "query": {}, + "buttonBehavior": "expand-searchfield", + "isSearchFieldHidden": false }, "innerBlocks": [] }