@wordpress/block-library
Version:
Block library for the WordPress editor.
8 lines (7 loc) • 12.2 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../../src/query/edit/inspector-controls/taxonomy-controls.js"],
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {\n\tFormTokenField,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { useSelect } from '@wordpress/data';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useState, useEffect, Fragment } from '@wordpress/element';\nimport { useDebounce } from '@wordpress/compose';\nimport { decodeEntities } from '@wordpress/html-entities';\nimport { sprintf, __ } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { useTaxonomies } from '../../utils';\n\nconst EMPTY_ARRAY = [];\nconst BASE_QUERY = {\n\torder: 'asc',\n\t_fields: 'id,name',\n\tcontext: 'view',\n};\n\n// Helper function to get the term id based on user input in terms `FormTokenField`.\nconst getTermIdByTermValue = ( terms, termValue ) => {\n\t// First we check for exact match by `term.id` or case sensitive `term.name` match.\n\tconst termId =\n\t\ttermValue?.id || terms?.find( ( term ) => term.name === termValue )?.id;\n\tif ( termId ) {\n\t\treturn termId;\n\t}\n\n\t/**\n\t * Here we make an extra check for entered terms in a non case sensitive way,\n\t * to match user expectations, due to `FormTokenField` behaviour that shows\n\t * suggestions which are case insensitive.\n\t *\n\t * Although WP tries to discourage users to add terms with the same name (case insensitive),\n\t * it's still possible if you manually change the name, as long as the terms have different slugs.\n\t * In this edge case we always apply the first match from the terms list.\n\t */\n\tconst termValueLower = termValue.toLocaleLowerCase();\n\treturn terms?.find(\n\t\t( term ) => term.name.toLocaleLowerCase() === termValueLower\n\t)?.id;\n};\n\nexport function TaxonomyControls( { onChange, query } ) {\n\tconst { postType, taxQuery } = query;\n\n\tconst taxonomies = useTaxonomies( postType );\n\tif ( ! taxonomies?.length ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<VStack spacing={ 4 }>\n\t\t\t{ taxonomies.map( ( taxonomy ) => {\n\t\t\t\tconst includeTermIds =\n\t\t\t\t\ttaxQuery?.include?.[ taxonomy.slug ] || [];\n\t\t\t\tconst excludeTermIds =\n\t\t\t\t\ttaxQuery?.exclude?.[ taxonomy.slug ] || [];\n\t\t\t\tconst onChangeTaxQuery = (\n\t\t\t\t\tnewTermIds,\n\t\t\t\t\t/** @type {'include'|'exclude'} */ key\n\t\t\t\t) => {\n\t\t\t\t\tconst newPartialTaxQuery = {\n\t\t\t\t\t\t...taxQuery?.[ key ],\n\t\t\t\t\t\t[ taxonomy.slug ]: newTermIds,\n\t\t\t\t\t};\n\t\t\t\t\t// Remove empty arrays from the partial `taxQuery` (include|exclude).\n\t\t\t\t\tif ( ! newTermIds.length ) {\n\t\t\t\t\t\tdelete newPartialTaxQuery[ taxonomy.slug ];\n\t\t\t\t\t}\n\t\t\t\t\tconst newTaxQuery = {\n\t\t\t\t\t\t...taxQuery,\n\t\t\t\t\t\t[ key ]: !! Object.keys( newPartialTaxQuery ).length\n\t\t\t\t\t\t\t? newPartialTaxQuery\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t};\n\t\t\t\t\tonChange( {\n\t\t\t\t\t\t// Clean up `taxQuery` if all filters are removed.\n\t\t\t\t\t\ttaxQuery: Object.values( newTaxQuery ).every(\n\t\t\t\t\t\t\t( value ) => ! value\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t: newTaxQuery,\n\t\t\t\t\t} );\n\t\t\t\t};\n\t\t\t\treturn (\n\t\t\t\t\t<Fragment key={ taxonomy.slug }>\n\t\t\t\t\t\t<TaxonomyItem\n\t\t\t\t\t\t\ttaxonomy={ taxonomy }\n\t\t\t\t\t\t\ttermIds={ includeTermIds }\n\t\t\t\t\t\t\toppositeTermIds={ excludeTermIds }\n\t\t\t\t\t\t\tonChange={ ( value ) =>\n\t\t\t\t\t\t\t\tonChangeTaxQuery( value, 'include' )\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlabel={ taxonomy.name }\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<TaxonomyItem\n\t\t\t\t\t\t\ttaxonomy={ taxonomy }\n\t\t\t\t\t\t\ttermIds={ excludeTermIds }\n\t\t\t\t\t\t\toppositeTermIds={ includeTermIds }\n\t\t\t\t\t\t\tonChange={ ( value ) =>\n\t\t\t\t\t\t\t\tonChangeTaxQuery( value, 'exclude' )\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlabel={\n\t\t\t\t\t\t\t\t/* translators: %s: taxonomy name */\n\t\t\t\t\t\t\t\tsprintf( __( 'Exclude: %s' ), taxonomy.name )\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</Fragment>\n\t\t\t\t);\n\t\t\t} ) }\n\t\t</VStack>\n\t);\n}\n\n/**\n * Renders a `FormTokenField` for a given taxonomy.\n *\n * @param {Object} props The props for the component.\n * @param {Object} props.taxonomy The taxonomy object.\n * @param {number[]} props.termIds An array with the block's term ids for the given taxonomy.\n * @param {number[]} props.oppositeTermIds An array with the opposite control's term ids (to exclude from suggestions).\n * @param {Function} props.onChange Callback `onChange` function.\n * @param {string} props.label Label of the control.\n * @return {JSX.Element} The rendered component.\n */\nfunction TaxonomyItem( {\n\ttaxonomy,\n\ttermIds,\n\toppositeTermIds,\n\tonChange,\n\tlabel,\n} ) {\n\tconst [ search, setSearch ] = useState( '' );\n\tconst [ value, setValue ] = useState( EMPTY_ARRAY );\n\tconst [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );\n\tconst debouncedSearch = useDebounce( setSearch, 250 );\n\tconst { searchResults, searchHasResolved } = useSelect(\n\t\t( select ) => {\n\t\t\tif ( ! search ) {\n\t\t\t\treturn { searchResults: EMPTY_ARRAY, searchHasResolved: true };\n\t\t\t}\n\t\t\tconst { getEntityRecords, hasFinishedResolution } =\n\t\t\t\tselect( coreStore );\n\n\t\t\t// Combine current terms and opposite terms for exclusion, to prevent\n\t\t\t// users from selecting the same term in both include and exclude controls.\n\t\t\tconst combinedExclude = [ ...termIds, ...oppositeTermIds ];\n\n\t\t\tconst selectorArgs = [\n\t\t\t\t'taxonomy',\n\t\t\t\ttaxonomy.slug,\n\t\t\t\t{\n\t\t\t\t\t...BASE_QUERY,\n\t\t\t\t\tsearch,\n\t\t\t\t\torderby: 'name',\n\t\t\t\t\texclude: combinedExclude,\n\t\t\t\t\tper_page: 20,\n\t\t\t\t},\n\t\t\t];\n\t\t\treturn {\n\t\t\t\tsearchResults: getEntityRecords( ...selectorArgs ),\n\t\t\t\tsearchHasResolved: hasFinishedResolution(\n\t\t\t\t\t'getEntityRecords',\n\t\t\t\t\tselectorArgs\n\t\t\t\t),\n\t\t\t};\n\t\t},\n\t\t[ search, taxonomy.slug, termIds, oppositeTermIds ]\n\t);\n\t// `existingTerms` are the ones fetched from the API and their type is `{ id: number; name: string }`.\n\t// They are used to extract the terms' names to populate the `FormTokenField` properly\n\t// and to sanitize the provided `termIds`, by setting only the ones that exist.\n\tconst existingTerms = useSelect(\n\t\t( select ) => {\n\t\t\tif ( ! termIds?.length ) {\n\t\t\t\treturn EMPTY_ARRAY;\n\t\t\t}\n\t\t\tconst { getEntityRecords } = select( coreStore );\n\t\t\treturn getEntityRecords( 'taxonomy', taxonomy.slug, {\n\t\t\t\t...BASE_QUERY,\n\t\t\t\tinclude: termIds,\n\t\t\t\tper_page: termIds.length,\n\t\t\t} );\n\t\t},\n\t\t[ taxonomy.slug, termIds ]\n\t);\n\t// Update the `value` state only after the selectors are resolved\n\t// to avoid emptying the input when we're changing terms.\n\tuseEffect( () => {\n\t\tif ( ! termIds?.length ) {\n\t\t\tsetValue( EMPTY_ARRAY );\n\t\t}\n\t\tif ( ! existingTerms?.length ) {\n\t\t\treturn;\n\t\t}\n\t\t// Returns only the existing entity ids. This prevents the component\n\t\t// from crashing in the editor, when non existing ids are provided.\n\t\tconst sanitizedValue = termIds.reduce( ( accumulator, id ) => {\n\t\t\tconst entity = existingTerms.find( ( term ) => term.id === id );\n\t\t\tif ( entity ) {\n\t\t\t\taccumulator.push( {\n\t\t\t\t\tid,\n\t\t\t\t\tvalue: entity.name,\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn accumulator;\n\t\t}, [] );\n\t\tsetValue( sanitizedValue );\n\t}, [ termIds, existingTerms ] );\n\t// Update suggestions only when the query has resolved.\n\tuseEffect( () => {\n\t\tif ( ! searchHasResolved ) {\n\t\t\treturn;\n\t\t}\n\t\tsetSuggestions( searchResults.map( ( result ) => result.name ) );\n\t}, [ searchResults, searchHasResolved ] );\n\tconst onTermsChange = ( newTermValues ) => {\n\t\tconst newTermIds = new Set();\n\t\tfor ( const termValue of newTermValues ) {\n\t\t\tconst termId = getTermIdByTermValue( searchResults, termValue );\n\t\t\tif ( termId ) {\n\t\t\t\tnewTermIds.add( termId );\n\t\t\t}\n\t\t}\n\t\tsetSuggestions( EMPTY_ARRAY );\n\t\tonChange( Array.from( newTermIds ) );\n\t};\n\treturn (\n\t\t<div className=\"block-library-query-inspector__taxonomy-control\">\n\t\t\t<FormTokenField\n\t\t\t\tlabel={ label }\n\t\t\t\tvalue={ value }\n\t\t\t\tonInputChange={ debouncedSearch }\n\t\t\t\tsuggestions={ suggestions }\n\t\t\t\tdisplayTransform={ decodeEntities }\n\t\t\t\tonChange={ onTermsChange }\n\t\t\t\t__experimentalShowHowTo={ false }\n\t\t\t\t__next40pxDefaultSize\n\t\t\t/>\n\t\t</div>\n\t);\n}\n"],
"mappings": "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",
"names": []
}