diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index 126382f85a513e..5b9764c8377426 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -34,6 +34,10 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-media-processing', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalMediaProcessing = true', 'before' ); } + + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-search-query-block', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalSearchQueryBlock = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 27a54b920f4d52..9de707fb0d2db0 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -175,6 +175,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-search-query-block', + __( 'Instant Search and Query Block', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable instant search functionality of the Search + Query blocks.', 'gutenberg' ), + 'id' => 'gutenberg-search-query-block', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-library/src/post-template/index.php b/packages/block-library/src/post-template/index.php index 9126355c096a57..feb8926118c94f 100644 --- a/packages/block-library/src/post-template/index.php +++ b/packages/block-library/src/post-template/index.php @@ -50,6 +50,11 @@ function render_block_core_post_template( $attributes, $content, $block ) { $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; + $search_query = empty( $_GET['instant-search'] ) ? '' : sanitize_text_field( $_GET['instant-search'] ); + + // Check if the Instant Search experiment is enabled. + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + $instant_search_enabled = isset( $gutenberg_experiments['gutenberg-search-query-block'] ) && $gutenberg_experiments['gutenberg-search-query-block']; // Use global query if needed. $use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ); @@ -67,9 +72,27 @@ function render_block_core_post_template( $attributes, $content, $block ) { } else { $query = $wp_query; } + + /* + * If the following conditions are met, run a new query with the search query: + * 1. Enhanced pagination is on. + * 2. Instant search is enabled. + * 3. The search query is not empty. + * 4. The query already has posts. + */ + if ( $enhanced_pagination && $instant_search_enabled && ! empty( $search_query ) && $query->have_posts() ) { + $args = array_merge( $query->query_vars, array( 's' => $search_query ) ); + $query = new WP_Query( $args ); + } } else { $query_args = build_query_vars_from_query_block( $block, $page ); - $query = new WP_Query( $query_args ); + + // Add search parameter if enhanced pagination is on and search query exists + if ( $enhanced_pagination && $instant_search_enabled && ! empty( $search_query ) ) { + $query_args['s'] = $search_query; + } + + $query = new WP_Query( $query_args ); } if ( ! $query->have_posts() ) { diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json index c5af5a29d21beb..d56c4b8809ccf8 100644 --- a/packages/block-library/src/search/block.json +++ b/packages/block-library/src/search/block.json @@ -48,6 +48,7 @@ "default": false } }, + "usesContext": [ "enhancedPagination" ], "supports": { "align": [ "left", "center", "right" ], "color": { diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index e4259bb0ce2c7f..b21e3632ceef9b 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -16,7 +16,7 @@ * * @return string The search block markup. */ -function render_block_core_search( $attributes ) { +function render_block_core_search( $attributes, $content, $block ) { // Older versions of the Search block defaulted the label and buttonText // attributes to `__( 'Search' )` meaning that many posts contain ``. Support these by defaulting an undefined label and @@ -48,6 +48,8 @@ function render_block_core_search( $attributes ) { // This variable is a constant and its value is always false at this moment. // It is defined this way because some values depend on it, in case it changes in the future. $open_by_default = false; + // Check if the block is using the enhanced pagination. + $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; $label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] ); $label = new WP_HTML_Tag_Processor( sprintf( '', $inline_styles['label'], $label_inner_html ) ); @@ -79,9 +81,11 @@ function render_block_core_search( $attributes ) { // If it's interactive, enqueue the script module and add the directives. $is_expandable_searchfield = 'button-only' === $button_position; - if ( $is_expandable_searchfield ) { + if ( $is_expandable_searchfield || $enhanced_pagination ) { wp_enqueue_script_module( '@wordpress/block-library/search/view' ); + } + if ( $is_expandable_searchfield ) { $input->set_attribute( 'data-wp-bind--aria-hidden', '!context.isSearchInputVisible' ); $input->set_attribute( 'data-wp-bind--tabindex', 'state.tabindex' ); @@ -90,6 +94,11 @@ function render_block_core_search( $attributes ) { $input->set_attribute( 'aria-hidden', 'true' ); $input->set_attribute( 'tabindex', '-1' ); } + // Instant search is only available when using the enhanced pagination. + if ( $enhanced_pagination ) { + $input->set_attribute( 'data-wp-bind--value', 'state.search' ); + $input->set_attribute( 'data-wp-on--input', 'actions.updateSearch' ); + } } if ( count( $query_params ) > 0 ) { @@ -165,20 +174,32 @@ function render_block_core_search( $attributes ) { $form_directives = ''; // If it's interactive, add the directives. + if ( $is_expandable_searchfield || $enhanced_pagination ) { + $form_directives = 'data-wp-interactive="core/search"'; + } + + // Adding wp_interactivity_state for the search block. + if ( $enhanced_pagination ) { + wp_interactivity_state( + 'core/search', + array( + 'search' => isset( $_GET['instant-search'] ) ? $_GET['instant-search'] : '', + ) + ); + } + if ( $is_expandable_searchfield ) { $aria_label_expanded = __( 'Submit Search' ); $aria_label_collapsed = __( 'Expand search field' ); $form_context = wp_interactivity_data_wp_context( array( - 'isSearchInputVisible' => $open_by_default, - 'inputId' => $input_id, - 'ariaLabelExpanded' => $aria_label_expanded, - 'ariaLabelCollapsed' => $aria_label_collapsed, + 'isSearchInputInitiallyVisible' => $open_by_default, + 'inputId' => $input_id, + 'ariaLabelExpanded' => $aria_label_expanded, + 'ariaLabelCollapsed' => $aria_label_collapsed, ) ); - $form_directives = ' - data-wp-interactive="core/search"' - . $form_context . + $form_directives .= $form_context . 'data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible" data-wp-on-async--keydown="actions.handleSearchKeydown" data-wp-on-async--focusout="actions.handleSearchFocusout" diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js index 0e4c462a2e3213..91b31e771e65a7 100644 --- a/packages/block-library/src/search/view.js +++ b/packages/block-library/src/search/view.js @@ -3,7 +3,11 @@ */ import { store, getContext, getElement } from '@wordpress/interactivity'; -const { actions } = store( +const isEmpty = ( obj ) => + [ Object, Array ].includes( ( obj || {} ).constructor ) && + ! Object.entries( obj || {} ).length; + +const { state, actions } = store( 'core/search', { state: { @@ -29,14 +33,25 @@ const { actions } = store( const { isSearchInputVisible } = getContext(); return isSearchInputVisible ? '0' : '-1'; }, + get isSearchInputVisible() { + const ctx = getContext(); + + // `ctx.isSearchInputVisible` is a client-side-only context value, so + // if it's not set, it means that it's an initial page load, so we need + // to return the value of `ctx.isSearchInputInitiallyVisible`. + if ( typeof ctx.isSearchInputVisible === 'undefined' ) { + return ctx.isSearchInputInitiallyVisible; + } + return ctx.isSearchInputVisible; + }, }, actions: { openSearchInput( event ) { - const ctx = getContext(); - const { ref } = getElement(); - if ( ! ctx.isSearchInputVisible ) { + if ( ! state.isSearchInputVisible ) { event.preventDefault(); + const ctx = getContext(); ctx.isSearchInputVisible = true; + const { ref } = getElement(); ref.parentElement.querySelector( 'input' ).focus(); } }, @@ -66,6 +81,32 @@ const { actions } = store( actions.closeSearchInput(); } }, + *updateSearch() { + const { ref } = getElement(); + const { value } = ref; + + // Don't navigate if the search didn't really change. + if ( value === state.search ) { + return; + } + + const url = new URL( window.location ); + + if ( ! isEmpty( value ) ) { + state.search = value; + url.searchParams.set( 'instant-search', value ); + } else { + url.searchParams.delete( 'instant-search' ); + } + + const { actions: routerActions } = yield import( + '@wordpress/interactivity-router' + ); + + routerActions.navigate( + `${ window.location.pathname }${ url.search }` + ); + }, }, }, { lock: true }