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

Query Filter: Update to match new Interactivity API format #563

Merged
merged 4 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions mu-plugins/blocks/query-filter/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
'hasHover' => false,
'hasMultiple' => $has_multiple,
];
$encoded_state = wp_json_encode( [ 'wporg' => [ 'queryFilter' => $init_state ] ] );
$encoded_state = wp_json_encode( $init_state );

// Set up a unique ID for this filter.
$html_id = wp_unique_id( "filter-{$settings['key']}-" );
Expand All @@ -66,43 +66,43 @@
?>
<div
<?php echo get_block_wrapper_attributes(); // phpcs:ignore ?>
data-wp-interactive
data-wp-interactive="<?php echo esc_attr( '{"namespace":"wporg/query-filter"}' ); ?>"
data-wp-context="<?php echo esc_attr( $encoded_state ); ?>"
data-wp-effect="effects.wporg.queryFilter.init"
data-wp-class--is-modal-open="context.wporg.queryFilter.isOpen"
data-wp-on--keydown="actions.wporg.queryFilter.handleKeydown"
data-wp-watch="effects.init"
data-wp-class--is-modal-open="context.isOpen"
data-wp-on--keydown="actions.handleKeydown"
>
<button
class="<?php echo esc_attr( implode( ' ', $button_classes ) ); ?>"
data-wp-class--is-active="context.wporg.queryFilter.isOpen"
data-wp-on--click="actions.wporg.queryFilter.toggle"
data-wp-bind--aria-expanded="context.wporg.queryFilter.isOpen"
data-wp-class--is-active="context.isOpen"
data-wp-on--click="actions.toggle"
data-wp-bind--aria-expanded="context.isOpen"
aria-controls="<?php echo esc_attr( $html_id ); ?>"
><?php echo wp_kses_post( $settings['label'] ); ?></button>

<div
class="wporg-query-filter__modal-backdrop"
data-wp-bind--hidden="!context.wporg.queryFilter.isOpen"
data-wp-on--click="actions.wporg.queryFilter.toggle"
data-wp-bind--hidden="!context.isOpen"
data-wp-on--click="actions.toggle"
></div>

<div
class="wporg-query-filter__modal"
id="<?php echo esc_attr( $html_id ); ?>"
data-wp-bind--hidden="!context.wporg.queryFilter.isOpen"
data-wp-effect--focus="effects.wporg.queryFilter.focusFirstElement"
data-wp-effect--position="effects.wporg.queryFilter.checkPosition"
data-wp-bind--hidden="!context.isOpen"
data-wp-effect--focus="effects.focusFirstElement"
data-wp-effect--position="effects.checkPosition"
>
<form
action="<?php echo esc_attr( $settings['action'] ); ?>"
data-wp-on--change="actions.wporg.queryFilter.handleFormChange"
data-wp-on--change="actions.handleFormChange"
>
<div class="wporg-query-filter__modal-header">
<h2><?php echo wp_kses_post( $settings['title'] ); ?></h2>
<input
type="button"
class="wporg-query-filter__modal-close"
data-wp-on--click="actions.wporg.queryFilter.toggle"
data-wp-on--click="actions.toggle"
aria-label="<?php esc_attr_e( 'Close', 'wporg' ); ?>"
/>
</div> <!-- /.wporg-query-filter__modal-header -->
Expand Down Expand Up @@ -140,18 +140,23 @@ class="wporg-query-filter__modal-close"
* @param WP_Block $block The current block being rendered.
*/
do_action( 'wporg_query_filter_in_form', $settings['key'], $block );

/* translators: %s is count of currently selected filters. */
$label_count = __( 'Apply (%s)', 'wporg' );
?>

<div class="wporg-query-filter__modal-actions">
<input
type="button"
class="wporg-query-filter__modal-action-clear"
value="<?php esc_attr_e( 'Clear', 'wporg' ); ?>"
data-wp-on--click="actions.wporg.queryFilter.clearSelection"
data-wp-on--click="actions.clearSelection"
aria-disabled="<?php echo $selected_count ? 'false' : 'true'; ?>"
/>
<input
type="submit"
data-label-with-count="<?php echo esc_attr( $label_count ); ?>"
data-label=<?php esc_attr_e( 'Apply', 'wporg' ); ?>
value="<?php echo esc_html( $apply_label ); ?>"
/>
</div> <!-- /.wporg-query-filter__modal-actions -->
Expand Down
4 changes: 2 additions & 2 deletions mu-plugins/blocks/query-filter/src/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
}
},
"editorScript": "file:./index.js",
"style": "file:./style.css",
"viewScript": "file:./view.js",
"style": "file:./style-index.css",
"viewModule": "file:./view.js",
"render": "file:../render.php"
}
1 change: 1 addition & 0 deletions mu-plugins/blocks/query-filter/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useBlockProps } from '@wordpress/block-editor';
* Internal dependencies
*/
import metadata from './block.json';
import './style.pcss';

function Edit( { attributes, name } ) {
return (
Expand Down
252 changes: 120 additions & 132 deletions mu-plugins/blocks/query-filter/src/view.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module build is not compatible with importing from other @wordpress/* packages, trying was giving me this error:

Module not found: Error: Attempted to use WordPress script in a module: @wordpress/i18n, which is not supported yet.

The workaround is to pass the label as a data attribute on the element from render.php. I don't think we'll need to do this forever, as this is currently being iterated on.

import { store as wpStore } from '@wordpress/interactivity';
import { getContext, getElement, store } from '@wordpress/interactivity';

// See https://github.com/WordPress/gutenberg/blob/37f52ae884a40f7cb77ac2484648b4e4ad973b59/packages/block-library/src/navigation/view-interactivity.js
const focusableSelectors = [
Expand All @@ -15,151 +14,140 @@ const focusableSelectors = [
'[tabindex]:not([tabindex^="-"])',
];

/**
* Toggles the overflow-x style of the query filter between 'hidden' and 'scroll'.
*
* In certain themes (e.g., showcase), an 'overflow-x: scroll' is added on mobile screens to always display
* the horizontal scrollbar, indicating to users that there's more content to the right.
* However, this persistent display feature causes the dropdown menu to be overlaid by the scrollbar
* when opened (See issue https://github.com/WordPress/wporg-mu-plugins/issues/467#issuecomment-1754349676).
* This function serves to address that issue.
*
*/
function toggleOverflowX() {
const filtersElement = document.querySelector( '.wporg-query-filters' );
const { actions } = store( 'wporg/query-filter', {
actions: {
/**
* Toggles the overflow-x style of the query filter between 'hidden' and 'scroll'.
*
* In certain themes (e.g., showcase), an 'overflow-x: scroll' is added on mobile screens to always display
* the horizontal scrollbar, indicating to users that there's more content to the right.
* However, this persistent display feature causes the dropdown menu to be overlaid by the scrollbar
* when opened (See issue https://github.com/WordPress/wporg-mu-plugins/issues/467#issuecomment-1754349676).
* This function serves to address that issue.
*
*/
toggleOverflowX: () => {
const filtersElement = document.querySelector( '.wporg-query-filters' );

if ( filtersElement ) {
const currentOverflowX = window.getComputedStyle( filtersElement ).overflowX;
if ( filtersElement ) {
const currentOverflowX = window.getComputedStyle( filtersElement ).overflowX;

if ( 'hidden' === currentOverflowX ) {
filtersElement.style.overflowX = 'scroll';
} else if ( 'scroll' === currentOverflowX || 'auto' === currentOverflowX ) {
filtersElement.style.overflowX = 'hidden';
}
}
}
if ( 'hidden' === currentOverflowX ) {
filtersElement.style.overflowX = 'scroll';
} else if ( 'scroll' === currentOverflowX || 'auto' === currentOverflowX ) {
filtersElement.style.overflowX = 'hidden';
}
}
},

function closeDropdown( store ) {
const { context } = store;
context.wporg.queryFilter.isOpen = false;
context.wporg.queryFilter.form?.reset();
closeDropdown: () => {
const context = getContext();
context.isOpen = false;
context.form?.reset();

const count = context.wporg.queryFilter.form?.querySelectorAll( 'input:checked' ).length;
updateButtons( store, count );
document.documentElement.classList.remove( 'is-query-filter-open' );
const count = context.form?.querySelectorAll( 'input:checked' ).length;
actions.updateButtons( count );
document.documentElement.classList.remove( 'is-query-filter-open' );

toggleOverflowX();
}
actions.toggleOverflowX();
},

function updateButtons( store, count ) {
const { context } = store;
if ( ! context.wporg.queryFilter.form ) {
return;
}
updateButtons: ( count ) => {
const context = getContext();
if ( ! context.form ) {
return;
}

const applyButton = context.wporg.queryFilter.form.querySelector( 'input[type="submit"]' );
const clearButton = context.wporg.queryFilter.form.querySelector( '.wporg-query-filter__modal-action-clear' );
const applyButton = context.form.querySelector( 'input[type="submit"]' );
const clearButton = context.form.querySelector( '.wporg-query-filter__modal-action-clear' );

// Only update the apply button if multiple selections are allowed.
if ( context.wporg.queryFilter.hasMultiple ) {
if ( count ) {
/* translators: %s is count of currently selected filters. */
applyButton.value = sprintf( __( 'Apply (%s)', 'wporg' ), count );
} else {
applyButton.value = __( 'Apply', 'wporg' );
}
}
// Only update the apply button if multiple selections are allowed.
if ( context.hasMultiple ) {
if ( count ) {
applyButton.value = applyButton.dataset.labelWithCount.replace( '%s', count );
} else {
applyButton.value = applyButton.dataset.label;
}
}

clearButton.setAttribute( 'aria-disabled', count ? 'false' : 'true' );
}
clearButton.setAttribute( 'aria-disabled', count ? 'false' : 'true' );
},

wpStore( {
actions: {
wporg: {
queryFilter: {
toggle: ( store ) => {
const { context } = store;
if ( context.wporg.queryFilter.isOpen ) {
closeDropdown( store );
} else {
context.wporg.queryFilter.isOpen = true;
document.documentElement.classList.add( 'is-query-filter-open' );
toggleOverflowX();
}
},
handleKeydown: ( store ) => {
const { context, event } = store;
// If Escape close the dropdown.
if ( event.key === 'Escape' ) {
closeDropdown( store );
context.wporg.queryFilter.toggleButton.focus();
return;
}
toggle: () => {
const context = getContext();
if ( context.isOpen ) {
actions.closeDropdown();
} else {
context.isOpen = true;
document.documentElement.classList.add( 'is-query-filter-open' );
actions.toggleOverflowX();
}
},
handleKeydown: ( event ) => {
const context = getContext();
// If Escape close the dropdown.
if ( event.key === 'Escape' ) {
actions.closeDropdown();
context.toggleButton.focus();
return;
}

// Trap focus.
if ( event.key === 'Tab' ) {
// If shift + tab it change the direction.
if (
event.shiftKey &&
window.document.activeElement === context.wporg.queryFilter.firstFocusableElement
) {
event.preventDefault();
context.wporg.queryFilter.lastFocusableElement.focus();
} else if (
! event.shiftKey &&
window.document.activeElement === context.wporg.queryFilter.lastFocusableElement
) {
event.preventDefault();
context.wporg.queryFilter.firstFocusableElement.focus();
}
}
},
handleFormChange: ( store ) => {
const { context } = store;
const count = context.wporg.queryFilter.form.querySelectorAll( 'input:checked' ).length;
updateButtons( store, count );
},
clearSelection: ( store ) => {
const { context, ref } = store;
if ( 'true' === ref.getAttribute( 'aria-disabled' ) ) {
return;
}
context.wporg.queryFilter.form
.querySelectorAll( 'input' )
.forEach( ( input ) => ( input.checked = false ) );
updateButtons( store, 0 );
},
},
// Trap focus.
if ( event.key === 'Tab' ) {
// If shift + tab it change the direction.
if ( event.shiftKey && window.document.activeElement === context.firstFocusableElement ) {
event.preventDefault();
context.lastFocusableElement.focus();
} else if ( ! event.shiftKey && window.document.activeElement === context.lastFocusableElement ) {
event.preventDefault();
context.firstFocusableElement.focus();
}
}
},
handleFormChange: () => {
const context = getContext();
const count = context.form.querySelectorAll( 'input:checked' ).length;
actions.updateButtons( count );
},
clearSelection: () => {
const context = getContext();
const { ref } = getElement();
if ( 'true' === ref.getAttribute( 'aria-disabled' ) ) {
return;
}
context.form.querySelectorAll( 'input' ).forEach( ( input ) => ( input.checked = false ) );
actions.updateButtons( 0 );
},
},
effects: {
wporg: {
queryFilter: {
init: ( { context, ref } ) => {
context.wporg.queryFilter.toggleButton = ref.querySelector( '.wporg-query-filter__toggle' );
context.wporg.queryFilter.form = ref.querySelector( 'form' );
init: () => {
const context = getContext();
const { ref } = getElement();
context.toggleButton = ref.querySelector( '.wporg-query-filter__toggle' );
context.form = ref.querySelector( 'form' );

if ( context.wporg.queryFilter.isOpen ) {
const focusableElements = ref.querySelectorAll( focusableSelectors );
context.wporg.queryFilter.firstFocusableElement = focusableElements[ 0 ];
context.wporg.queryFilter.lastFocusableElement =
focusableElements[ focusableElements.length - 1 ];
}
},
checkPosition: ( { context, ref } ) => {
if ( context.wporg.queryFilter.isOpen ) {
const position = ref.getBoundingClientRect();
if ( position.left < 0 ) {
ref.style.left = 0;
}
}
},
focusFirstElement: ( { context, ref } ) => {
if ( context.wporg.queryFilter.isOpen ) {
ref.querySelector( 'form input:first-child' ).focus();
}
},
},
if ( context.isOpen ) {
const focusableElements = ref.querySelectorAll( focusableSelectors );
context.firstFocusableElement = focusableElements[ 0 ];
context.lastFocusableElement = focusableElements[ focusableElements.length - 1 ];
}
},
checkPosition: () => {
const context = getContext();
const { ref } = getElement();
if ( context.isOpen ) {
const position = ref.getBoundingClientRect();
if ( position.left < 0 ) {
ref.style.left = 0;
}
}
},
focusFirstElement: () => {
const context = getContext();
const { ref } = getElement();
if ( context.isOpen ) {
ref.querySelector( 'form input:first-child' ).focus();
}
},
},
} );
Loading