Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search block: switch interactivity to the Interactivity API #53343

Merged
merged 31 commits into from Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1101255
Fist commit
cbravobernal Aug 4, 2023
2f84e4d
Come back to default style, as we updated wp-class directive
cbravobernal Aug 4, 2023
8093bb0
Remove not needed test
cbravobernal Aug 4, 2023
7028b57
Add interactions
cbravobernal Aug 4, 2023
f356798
Use `wp_store` to handle ARIA label
michalczaplinski Aug 9, 2023
462edb0
Move `FORM` directives directly to the HTML
SantosGuillamot Aug 9, 2023
99f681b
Move `aria-label` directive inside conditional
SantosGuillamot Aug 9, 2023
ff5eb0d
Change context variable name
SantosGuillamot Aug 9, 2023
d23428d
Add search button directives
SantosGuillamot Aug 9, 2023
80eaa0c
Add input directives and needed actions
SantosGuillamot Aug 9, 2023
afc0b70
Remove old functions
SantosGuillamot Aug 9, 2023
b75bd40
Load the search directives only if using Gutenberg
SantosGuillamot Aug 9, 2023
e9294a5
Change PHP formatting
SantosGuillamot Aug 9, 2023
39b1630
Fix PHP coding standard issues
SantosGuillamot Aug 9, 2023
6d01ab8
Remove trailing comma
SantosGuillamot Aug 9, 2023
347685f
Don't submit form when input is closed
SantosGuillamot Aug 9, 2023
00d1b4c
Focus button when closed with ESC key
SantosGuillamot Aug 9, 2023
3533cb0
Merge branch 'trunk' into experiment/move-search-block-interactivity
SantosGuillamot Aug 10, 2023
ab3fe06
Add selector in SSR
SantosGuillamot Aug 10, 2023
abb18f6
Format PHP
SantosGuillamot Aug 10, 2023
7d17805
Fix typo
SantosGuillamot Aug 11, 2023
262bca8
Add comment for `$open_by_default` variable
SantosGuillamot Aug 16, 2023
3f8bbdc
Merge branch 'trunk' into experiment/move-search-block-interactivity
SantosGuillamot Sep 4, 2023
ae7d054
Remove extra space
SantosGuillamot Sep 4, 2023
72426b0
Rename search block function
SantosGuillamot Sep 4, 2023
5a605d1
Add `supports.interactivity`
SantosGuillamot Sep 8, 2023
bf0938e
Remove old files and
SantosGuillamot Sep 8, 2023
128ae33
Add comment to manual SSR for core
SantosGuillamot Sep 11, 2023
a7686f6
Remove wp_store
SantosGuillamot Sep 11, 2023
e498b4c
Use `null` instead of `undefined
SantosGuillamot Sep 11, 2023
f040f3b
Remove unnecessary conditional
SantosGuillamot Sep 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Expand Up @@ -759,7 +759,7 @@ Help visitors find your content. ([Source](https://github.com/WordPress/gutenber

- **Name:** core/search
- **Category:** widgets
- **Supports:** align (center, left, right), color (background, gradients, text), typography (fontSize, lineHeight), ~~html~~
- **Supports:** align (center, left, right), color (background, gradients, text), interactivity, typography (fontSize, lineHeight), ~~html~~
- **Attributes:** buttonBehavior, buttonPosition, buttonText, buttonUseIcon, isSearchFieldHidden, label, placeholder, query, showLabel, width, widthUnit

## Separator
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/search/block.json
Expand Up @@ -62,6 +62,7 @@
"text": true
}
},
"interactivity": true,
"typography": {
"__experimentalSkipSerialization": true,
"__experimentalSelector": ".wp-block-search__label, .wp-block-search__input, .wp-block-search__button",
Expand Down
30 changes: 27 additions & 3 deletions packages/block-library/src/search/index.php
Expand Up @@ -46,6 +46,9 @@ function render_block_core_search( $attributes, $content, $block ) {
'button-inside' === $attributes['buttonPosition'];
// Border color classes need to be applied to the elements that have a border color.
$border_color_classes = get_border_color_classes_for_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';

$label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] );
$label = new WP_HTML_Tag_Processor( sprintf( '<label %1$s>%2$s</label>', $inline_styles['label'], $label_inner_html ) );
Expand Down Expand Up @@ -77,6 +80,9 @@ function render_block_core_search( $attributes, $content, $block ) {

$is_expandable_searchfield = 'button-only' === $button_position && 'expand-searchfield' === $button_behavior;
if ( $is_expandable_searchfield ) {
$input->set_attribute( 'data-wp-bind--aria-hidden', '!context.core.search.isSearchInputVisible' );
$input->set_attribute( 'data-wp-bind--tabindex', 'selectors.core.search.tabindex' );
// Adding these attributes manually is needed until the Interactivity API SSR logic is added to core.
$input->set_attribute( 'aria-hidden', 'true' );
$input->set_attribute( 'tabindex', '-1' );
}
Expand Down Expand Up @@ -139,11 +145,16 @@ function render_block_core_search( $attributes, $content, $block ) {
if ( $button->next_tag() ) {
$button->add_class( implode( ' ', $button_classes ) );
if ( 'expand-searchfield' === $attributes['buttonBehavior'] && 'button-only' === $attributes['buttonPosition'] ) {
$button->set_attribute( 'data-wp-bind--aria-label', 'selectors.core.search.ariaLabel' );
$button->set_attribute( 'data-wp-bind--aria-controls', 'selectors.core.search.ariaControls' );
$button->set_attribute( 'data-wp-bind--aria-expanded', 'context.core.search.isSearchInputVisible' );
$button->set_attribute( 'data-wp-bind--type', 'selectors.core.search.type' );
$button->set_attribute( 'data-wp-on--click', 'actions.core.search.openSearchInput' );
// Adding these attributes manually is needed until the Interactivity API SSR logic is added to core.
$button->set_attribute( 'aria-label', __( 'Expand search field' ) );
$button->set_attribute( 'data-toggled-aria-label', __( 'Submit Search' ) );
$button->set_attribute( 'aria-controls', 'wp-block-search__input-' . $input_id );
$button->set_attribute( 'aria-expanded', 'false' );
$button->set_attribute( 'type', 'button' ); // Will be set to submit after clicking.
$button->set_attribute( 'type', 'button' );
} else {
$button->set_attribute( 'aria-label', wp_strip_all_tags( $attributes['buttonText'] ) );
}
Expand All @@ -160,11 +171,24 @@ function render_block_core_search( $attributes, $content, $block ) {
$wrapper_attributes = get_block_wrapper_attributes(
array( 'class' => $classnames )
);
$form_directives = '';
if ( $is_expandable_searchfield ) {
$aria_label_expanded = __( 'Submit Search' );
$aria_label_collapsed = __( 'Expand search field' );
$form_directives = '
data-wp-interactive
data-wp-context=\'{ "core": { "search": { "isSearchInputVisible": ' . $open_by_default . ', "inputId": "' . $input_id . '", "ariaLabelExpanded": "' . $aria_label_expanded . '", "ariaLabelCollapsed": "' . $aria_label_collapsed . '" } } }\'
data-wp-class--wp-block-search__searchfield-hidden="!context.core.search.isSearchInputVisible"
data-wp-on--keydown="actions.core.search.handleSearchKeydown"
data-wp-on--focusout="actions.core.search.handleSearchFocusout"
';
};

return sprintf(
'<form role="search" method="get" action="%s" %s>%s</form>',
'<form role="search" method="get" action="%1s" %2s %3s>%4s</form>',
esc_url( home_url( '/' ) ),
$wrapper_attributes,
$form_directives,
$label . $field_markup
);
}
Expand Down
239 changes: 70 additions & 169 deletions packages/block-library/src/search/view.js
@@ -1,172 +1,73 @@
/*eslint-env browser*/

/** @type {?HTMLFormElement} */
let expandedSearchBlock = null;

const hiddenClass = 'wp-block-search__searchfield-hidden';

/**
* Toggles aria-label with data-toggled-aria-label.
*
* @param {HTMLElement} element
*/
function toggleAriaLabel( element ) {
if ( ! ( 'toggledAriaLabel' in element.dataset ) ) {
throw new Error( 'Element lacks toggledAriaLabel in dataset.' );
}

const ariaLabel = element.dataset.toggledAriaLabel;
element.dataset.toggledAriaLabel = element.ariaLabel;
element.ariaLabel = ariaLabel;
}

/**
* Gets search input.
*
* @param {HTMLFormElement} block Search block.
* @return {HTMLInputElement} Search input.
*/
function getSearchInput( block ) {
return block.querySelector( '.wp-block-search__input' );
}

/**
* Gets search button.
*
* @param {HTMLFormElement} block Search block.
* @return {HTMLButtonElement} Search button.
*/
function getSearchButton( block ) {
return block.querySelector( '.wp-block-search__button' );
}

/**
* Handles keydown event to collapse an expanded Search block (when pressing Escape key).
*
* @param {KeyboardEvent} event
*/
function handleKeydownEvent( event ) {
if ( ! expandedSearchBlock ) {
// In case the event listener wasn't removed in time.
return;
}

if ( event.key === 'Escape' ) {
const block = expandedSearchBlock; // This is nullified by collapseExpandedSearchBlock().
collapseExpandedSearchBlock();
getSearchButton( block ).focus();
}
}

/**
* Handles keyup event to collapse an expanded Search block (e.g. when tabbing out of expanded Search block).
*
* @param {KeyboardEvent} event
*/
function handleKeyupEvent( event ) {
if ( ! expandedSearchBlock ) {
// In case the event listener wasn't removed in time.
return;
}

if ( event.target.closest( '.wp-block-search' ) !== expandedSearchBlock ) {
collapseExpandedSearchBlock();
}
}

/**
* Expands search block.
*
* Inverse of what is done in collapseExpandedSearchBlock().
*
* @param {HTMLFormElement} block Search block.
*/
function expandSearchBlock( block ) {
// Make sure only one is open at a time.
if ( expandedSearchBlock ) {
collapseExpandedSearchBlock();
}

const searchField = getSearchInput( block );
const searchButton = getSearchButton( block );

searchButton.type = 'submit';
searchField.ariaHidden = 'false';
searchField.tabIndex = 0;
searchButton.ariaExpanded = 'true';
searchButton.removeAttribute( 'aria-controls' ); // Note: Seemingly not reflected with searchButton.ariaControls.
toggleAriaLabel( searchButton );
block.classList.remove( hiddenClass );

searchField.focus(); // Note that Chrome seems to do this automatically.

// The following two must be inverse of what is done in collapseExpandedSearchBlock().
document.addEventListener( 'keydown', handleKeydownEvent, {
passive: true,
} );
document.addEventListener( 'keyup', handleKeyupEvent, {
passive: true,
} );

expandedSearchBlock = block;
}

/**
* Collapses the expanded search block.
*
* Inverse of what is done in expandSearchBlock().
* WordPress dependencies
*/
function collapseExpandedSearchBlock() {
if ( ! expandedSearchBlock ) {
throw new Error( 'Expected expandedSearchBlock to be defined.' );
}
const block = expandedSearchBlock;
const searchField = getSearchInput( block );
const searchButton = getSearchButton( block );

searchButton.type = 'button';
searchField.ariaHidden = 'true';
searchField.tabIndex = -1;
searchButton.ariaExpanded = 'false';
searchButton.setAttribute( 'aria-controls', searchField.id ); // Note: Seemingly not reflected with searchButton.ariaControls.
toggleAriaLabel( searchButton );
block.classList.add( hiddenClass );

// The following two must be inverse of what is done in expandSearchBlock().
document.removeEventListener( 'keydown', handleKeydownEvent, {
passive: true,
} );
document.removeEventListener( 'keyup', handleKeyupEvent, {
passive: true,
} );

expandedSearchBlock = null;
}

// Listen for click events anywhere on the document so this script can be loaded asynchronously in the head.
document.addEventListener(
'click',
( event ) => {
// Get the ancestor expandable Search block of the clicked element.
const block = event.target.closest(
'.wp-block-search__button-behavior-expand'
);

/*
* If there is already an expanded search block and either the current click was not for a Search block or it was
* for another block, then collapse the currently-expanded block.
*/
if ( expandedSearchBlock && block !== expandedSearchBlock ) {
collapseExpandedSearchBlock();
}

// If the click was on or inside a collapsed Search block, expand it.
if (
block instanceof HTMLFormElement &&
block.classList.contains( hiddenClass )
) {
expandSearchBlock( block );
}
import { store as wpStore } from '@wordpress/interactivity';

wpStore( {
selectors: {
core: {
search: {
ariaLabel: ( { context } ) => {
const { ariaLabelCollapsed, ariaLabelExpanded } =
context.core.search;
return context.core.search.isSearchInputVisible
? ariaLabelExpanded
: ariaLabelCollapsed;
},
ariaControls: ( { context } ) => {
return context.core.search.isSearchInputVisible
? null
: context.core.search.inputId;
},
type: ( { context } ) => {
return context.core.search.isSearchInputVisible
? 'submit'
: 'button';
},
tabindex: ( { context } ) => {
return context.core.search.isSearchInputVisible
? '0'
: '-1';
},
},
},
},
actions: {
core: {
search: {
openSearchInput: ( { context, event, ref } ) => {
if ( ! context.core.search.isSearchInputVisible ) {
event.preventDefault();
context.core.search.isSearchInputVisible = true;
ref.parentElement.querySelector( 'input' ).focus();
}
},
closeSearchInput: ( { context } ) => {
context.core.search.isSearchInputVisible = false;
},
handleSearchKeydown: ( store ) => {
const { actions, event, ref } = store;
// If Escape close the menu.
if ( event?.key === 'Escape' ) {
actions.core.search.closeSearchInput( store );
ref.querySelector( 'button' ).focus();
}
},
handleSearchFocusout: ( store ) => {
const { actions, event, ref } = store;
// If focus is outside search form, and in the document, close menu
// event.target === The element losing focus
// event.relatedTarget === The element receiving focus (if any)
// When focusout is outside the document,
// `window.document.activeElement` doesn't change.
if (
! ref.contains( event.relatedTarget ) &&
event.target !== window.document.activeElement
) {
actions.core.search.closeSearchInput( store );
}
},
},
},
},
{ passive: true }
);
} );