/** * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { useCollection, useQueryStateByKey, useQueryStateByContext, useCollectionData, usePrevious, useShallowEqual, } from '@woocommerce/base-hooks'; import { useCallback, useEffect, useState, useMemo } from '@wordpress/element'; import CheckboxList from '@woocommerce/base-components/checkbox-list'; import DropdownSelector from '@woocommerce/base-components/dropdown-selector'; import FilterSubmitButton from '@woocommerce/base-components/filter-submit-button'; import isShallowEqual from '@wordpress/is-shallow-equal'; import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies */ import { getAttributeFromID } from '../../utils/attributes'; import { updateAttributeFilter } from '../../utils/attributes-query'; import Label from './label'; import { previewAttributeObject, previewOptions } from './preview'; import './style.scss'; /** * Component displaying an attribute filter. * * @param {Object} props Incoming props for the component. * @param {Object} props.attributes Incoming block attributes. * @param {boolean} props.isEditor */ const AttributeFilterBlock = ( { attributes: blockAttributes, isEditor = false, } ) => { const attributeObject = blockAttributes.isPreview && ! blockAttributes.attributeId ? previewAttributeObject : getAttributeFromID( blockAttributes.attributeId ); const [ checked, setChecked ] = useState( [] ); const [ displayedOptions, setDisplayedOptions ] = useState( blockAttributes.isPreview && ! blockAttributes.attributeId ? previewOptions : [] ); const [ queryState ] = useQueryStateByContext(); const [ productAttributesQuery, setProductAttributesQuery, ] = useQueryStateByKey( 'attributes', [] ); const { results: attributeTerms, isLoading: attributeTermsLoading, } = useCollection( { namespace: '/wc/store', resourceName: 'products/attributes/terms', resourceValues: [ attributeObject.id ], shouldSelect: blockAttributes.attributeId > 0, } ); const filterAvailableTerms = blockAttributes.displayStyle !== 'dropdown' && blockAttributes.queryType === 'and'; const { results: filteredCounts, isLoading: filteredCountsLoading, } = useCollectionData( { queryAttribute: { taxonomy: attributeObject.taxonomy, queryType: blockAttributes.queryType, }, queryState: { ...queryState, attributes: filterAvailableTerms ? queryState.attributes : null, }, } ); /** * Get count data about a given term by ID. */ const getFilteredTerm = useCallback( ( id ) => { if ( ! filteredCounts.attribute_counts ) { return null; } return filteredCounts.attribute_counts.find( ( { term } ) => term === id ); }, [ filteredCounts ] ); /** * Compare intersection of all terms and filtered counts to get a list of options to display. */ useEffect( () => { /** * Checks if a term slug is in the query state. * * @param {string} termSlug The term of the slug to check. */ const isTermInQueryState = ( termSlug ) => { if ( ! queryState?.attributes ) { return false; } return queryState.attributes.some( ( { attribute, slug = [] } ) => attribute === attributeObject.taxonomy && slug.includes( termSlug ) ); }; if ( attributeTermsLoading || filteredCountsLoading ) { return; } const newOptions = attributeTerms .map( ( term ) => { const filteredTerm = getFilteredTerm( term.id ); // If there is no match this term doesn't match the current product collection - only render if checked. if ( ! filteredTerm && ! checked.includes( term.slug ) && ! isTermInQueryState( term.slug ) ) { return null; } const count = filteredTerm ? filteredTerm.count : 0; return { value: term.slug, name: decodeEntities( term.name ), label: (