UNPKG

@wordpress/blocks

Version:
810 lines (786 loc) 25.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.__experimentalHasContentRoleAttribute = void 0; exports.getActiveBlockVariation = getActiveBlockVariation; exports.getBlockStyles = getBlockStyles; exports.getBlockSupport = void 0; exports.getBlockType = getBlockType; exports.getBlockVariations = exports.getBlockTypes = void 0; exports.getCategories = getCategories; exports.getChildBlockNames = void 0; exports.getCollections = getCollections; exports.getDefaultBlockName = getDefaultBlockName; exports.getDefaultBlockVariation = getDefaultBlockVariation; exports.getFreeformFallbackBlockName = getFreeformFallbackBlockName; exports.getGroupingBlockName = getGroupingBlockName; exports.getUnregisteredFallbackBlockName = getUnregisteredFallbackBlockName; exports.hasBlockSupport = hasBlockSupport; exports.hasChildBlocksWithInserterSupport = exports.hasChildBlocks = void 0; exports.isMatchingSearchTerm = isMatchingSearchTerm; var _removeAccents = _interopRequireDefault(require("remove-accents")); var _data = require("@wordpress/data"); var _richText = require("@wordpress/rich-text"); var _deprecated = _interopRequireDefault(require("@wordpress/deprecated")); var _utils = require("./utils"); var _privateSelectors = require("./private-selectors"); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ /** @typedef {import('../api/registration').WPBlockVariation} WPBlockVariation */ /** @typedef {import('../api/registration').WPBlockVariationScope} WPBlockVariationScope */ /** @typedef {import('./reducer').WPBlockCategory} WPBlockCategory */ /** * Given a block name or block type object, returns the corresponding * normalized block type object. * * @param {Object} state Blocks state. * @param {(string|Object)} nameOrType Block name or type object * * @return {Object} Block type object. */ const getNormalizedBlockType = (state, nameOrType) => 'string' === typeof nameOrType ? getBlockType(state, nameOrType) : nameOrType; /** * Returns all the available block types. * * @param {Object} state Data state. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const blockTypes = useSelect( * ( select ) => select( blocksStore ).getBlockTypes(), * [] * ); * * return ( * <ul> * { blockTypes.map( ( block ) => ( * <li key={ block.name }>{ block.title }</li> * ) ) } * </ul> * ); * }; * ``` * * @return {Array} Block Types. */ const getBlockTypes = exports.getBlockTypes = (0, _data.createSelector)(state => Object.values(state.blockTypes), state => [state.blockTypes]); /** * Returns a block type by name. * * @param {Object} state Data state. * @param {string} name Block type name. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const paragraphBlock = useSelect( ( select ) => * ( select ) => select( blocksStore ).getBlockType( 'core/paragraph' ), * [] * ); * * return ( * <ul> * { paragraphBlock && * Object.entries( paragraphBlock.supports ).map( * ( blockSupportsEntry ) => { * const [ propertyName, value ] = blockSupportsEntry; * return ( * <li * key={ propertyName } * >{ `${ propertyName } : ${ value }` }</li> * ); * } * ) } * </ul> * ); * }; * ``` * * @return {?Object} Block Type. */ function getBlockType(state, name) { return state.blockTypes[name]; } /** * Returns block styles by block name. * * @param {Object} state Data state. * @param {string} name Block type name. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const buttonBlockStyles = useSelect( ( select ) => * select( blocksStore ).getBlockStyles( 'core/button' ), * [] * ); * * return ( * <ul> * { buttonBlockStyles && * buttonBlockStyles.map( ( style ) => ( * <li key={ style.name }>{ style.label }</li> * ) ) } * </ul> * ); * }; * ``` * * @return {Array?} Block Styles. */ function getBlockStyles(state, name) { return state.blockStyles[name]; } /** * Returns block variations by block name. * * @param {Object} state Data state. * @param {string} blockName Block type name. * @param {WPBlockVariationScope} [scope] Block variation scope name. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const socialLinkVariations = useSelect( ( select ) => * select( blocksStore ).getBlockVariations( 'core/social-link' ), * [] * ); * * return ( * <ul> * { socialLinkVariations && * socialLinkVariations.map( ( variation ) => ( * <li key={ variation.name }>{ variation.title }</li> * ) ) } * </ul> * ); * }; * ``` * * @return {(WPBlockVariation[]|void)} Block variations. */ const getBlockVariations = exports.getBlockVariations = (0, _data.createSelector)((state, blockName, scope) => { const variations = state.blockVariations[blockName]; if (!variations || !scope) { return variations; } return variations.filter(variation => { // For backward compatibility reasons, variation's scope defaults to // `block` and `inserter` when not set. return (variation.scope || ['block', 'inserter']).includes(scope); }); }, (state, blockName) => [state.blockVariations[blockName]]); /** * Returns the active block variation for a given block based on its attributes. * Variations are determined by their `isActive` property. * Which is either an array of block attribute keys or a function. * * In case of an array of block attribute keys, the `attributes` are compared * to the variation's attributes using strict equality check. * * In case of function type, the function should accept a block's attributes * and the variation's attributes and determines if a variation is active. * A function that accepts a block's attributes and the variation's attributes and determines if a variation is active. * * @param {Object} state Data state. * @param {string} blockName Name of block (example: “core/columns”). * @param {Object} attributes Block attributes used to determine active variation. * @param {WPBlockVariationScope} [scope] Block variation scope name. * * @example * ```js * import { __ } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { store as blockEditorStore } from '@wordpress/block-editor'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * // This example assumes that a core/embed block is the first block in the Block Editor. * const activeBlockVariation = useSelect( ( select ) => { * // Retrieve the list of blocks. * const [ firstBlock ] = select( blockEditorStore ).getBlocks() * * // Return the active block variation for the first block. * return select( blocksStore ).getActiveBlockVariation( * firstBlock.name, * firstBlock.attributes * ); * }, [] ); * * return activeBlockVariation && activeBlockVariation.name === 'spotify' ? ( * <p>{ __( 'Spotify variation' ) }</p> * ) : ( * <p>{ __( 'Other variation' ) }</p> * ); * }; * ``` * * @return {(WPBlockVariation|undefined)} Active block variation. */ function getActiveBlockVariation(state, blockName, attributes, scope) { const variations = getBlockVariations(state, blockName, scope); if (!variations) { return variations; } const blockType = getBlockType(state, blockName); const attributeKeys = Object.keys(blockType?.attributes || {}); let match; let maxMatchedAttributes = 0; for (const variation of variations) { if (Array.isArray(variation.isActive)) { const definedAttributes = variation.isActive.filter(attribute => { // We support nested attribute paths, e.g. `layout.type`. // In this case, we need to check if the part before the // first dot is a known attribute. const topLevelAttribute = attribute.split('.')[0]; return attributeKeys.includes(topLevelAttribute); }); const definedAttributesLength = definedAttributes.length; if (definedAttributesLength === 0) { continue; } const isMatch = definedAttributes.every(attribute => { const variationAttributeValue = (0, _utils.getValueFromObjectPath)(variation.attributes, attribute); if (variationAttributeValue === undefined) { return false; } let blockAttributeValue = (0, _utils.getValueFromObjectPath)(attributes, attribute); if (blockAttributeValue instanceof _richText.RichTextData) { blockAttributeValue = blockAttributeValue.toHTMLString(); } return (0, _utils.matchesAttributes)(blockAttributeValue, variationAttributeValue); }); if (isMatch && definedAttributesLength > maxMatchedAttributes) { match = variation; maxMatchedAttributes = definedAttributesLength; } } else if (variation.isActive?.(attributes, variation.attributes)) { // If isActive is a function, we cannot know how many attributes it matches. // This means that we cannot compare the specificity of our matches, // and simply return the best match we have found. return match || variation; } } return match; } /** * Returns the default block variation for the given block type. * When there are multiple variations annotated as the default one, * the last added item is picked. This simplifies registering overrides. * When there is no default variation set, it returns the first item. * * @param {Object} state Data state. * @param {string} blockName Block type name. * @param {WPBlockVariationScope} [scope] Block variation scope name. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const defaultEmbedBlockVariation = useSelect( ( select ) => * select( blocksStore ).getDefaultBlockVariation( 'core/embed' ), * [] * ); * * return ( * defaultEmbedBlockVariation && ( * <p> * { sprintf( * __( 'core/embed default variation: %s' ), * defaultEmbedBlockVariation.title * ) } * </p> * ) * ); * }; * ``` * * @return {?WPBlockVariation} The default block variation. */ function getDefaultBlockVariation(state, blockName, scope) { const variations = getBlockVariations(state, blockName, scope); const defaultVariation = [...variations].reverse().find(({ isDefault }) => !!isDefault); return defaultVariation || variations[0]; } /** * Returns all the available block categories. * * @param {Object} state Data state. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect, } from '@wordpress/data'; * * const ExampleComponent = () => { * const blockCategories = useSelect( ( select ) => * select( blocksStore ).getCategories(), * [] * ); * * return ( * <ul> * { blockCategories.map( ( category ) => ( * <li key={ category.slug }>{ category.title }</li> * ) ) } * </ul> * ); * }; * ``` * * @return {WPBlockCategory[]} Categories list. */ function getCategories(state) { return state.categories; } /** * Returns all the available collections. * * @param {Object} state Data state. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const blockCollections = useSelect( ( select ) => * select( blocksStore ).getCollections(), * [] * ); * * return ( * <ul> * { Object.values( blockCollections ).length > 0 && * Object.values( blockCollections ).map( ( collection ) => ( * <li key={ collection.title }>{ collection.title }</li> * ) ) } * </ul> * ); * }; * ``` * * @return {Object} Collections list. */ function getCollections(state) { return state.collections; } /** * Returns the name of the default block name. * * @param {Object} state Data state. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const defaultBlockName = useSelect( ( select ) => * select( blocksStore ).getDefaultBlockName(), * [] * ); * * return ( * defaultBlockName && ( * <p> * { sprintf( __( 'Default block name: %s' ), defaultBlockName ) } * </p> * ) * ); * }; * ``` * * @return {?string} Default block name. */ function getDefaultBlockName(state) { return state.defaultBlockName; } /** * Returns the name of the block for handling non-block content. * * @param {Object} state Data state. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const freeformFallbackBlockName = useSelect( ( select ) => * select( blocksStore ).getFreeformFallbackBlockName(), * [] * ); * * return ( * freeformFallbackBlockName && ( * <p> * { sprintf( __( * 'Freeform fallback block name: %s' ), * freeformFallbackBlockName * ) } * </p> * ) * ); * }; * ``` * * @return {?string} Name of the block for handling non-block content. */ function getFreeformFallbackBlockName(state) { return state.freeformFallbackBlockName; } /** * Returns the name of the block for handling unregistered blocks. * * @param {Object} state Data state. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const unregisteredFallbackBlockName = useSelect( ( select ) => * select( blocksStore ).getUnregisteredFallbackBlockName(), * [] * ); * * return ( * unregisteredFallbackBlockName && ( * <p> * { sprintf( __( * 'Unregistered fallback block name: %s' ), * unregisteredFallbackBlockName * ) } * </p> * ) * ); * }; * ``` * * @return {?string} Name of the block for handling unregistered blocks. */ function getUnregisteredFallbackBlockName(state) { return state.unregisteredFallbackBlockName; } /** * Returns the name of the block for handling the grouping of blocks. * * @param {Object} state Data state. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const groupingBlockName = useSelect( ( select ) => * select( blocksStore ).getGroupingBlockName(), * [] * ); * * return ( * groupingBlockName && ( * <p> * { sprintf( * __( 'Default grouping block name: %s' ), * groupingBlockName * ) } * </p> * ) * ); * }; * ``` * * @return {?string} Name of the block for handling the grouping of blocks. */ function getGroupingBlockName(state) { return state.groupingBlockName; } /** * Returns an array with the child blocks of a given block. * * @param {Object} state Data state. * @param {string} blockName Block type name. * * @example * ```js * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const childBlockNames = useSelect( ( select ) => * select( blocksStore ).getChildBlockNames( 'core/navigation' ), * [] * ); * * return ( * <ul> * { childBlockNames && * childBlockNames.map( ( child ) => ( * <li key={ child }>{ child }</li> * ) ) } * </ul> * ); * }; * ``` * * @return {Array} Array of child block names. */ const getChildBlockNames = exports.getChildBlockNames = (0, _data.createSelector)((state, blockName) => { return getBlockTypes(state).filter(blockType => { return blockType.parent?.includes(blockName); }).map(({ name }) => name); }, state => [state.blockTypes]); /** * Returns the block support value for a feature, if defined. * * @param {Object} state Data state. * @param {(string|Object)} nameOrType Block name or type object * @param {Array|string} feature Feature to retrieve * @param {*} defaultSupports Default value to return if not * explicitly defined * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const paragraphBlockSupportValue = useSelect( ( select ) => * select( blocksStore ).getBlockSupport( 'core/paragraph', 'anchor' ), * [] * ); * * return ( * <p> * { sprintf( * __( 'core/paragraph supports.anchor value: %s' ), * paragraphBlockSupportValue * ) } * </p> * ); * }; * ``` * * @return {?*} Block support value */ const getBlockSupport = (state, nameOrType, feature, defaultSupports) => { const blockType = getNormalizedBlockType(state, nameOrType); if (!blockType?.supports) { return defaultSupports; } return (0, _utils.getValueFromObjectPath)(blockType.supports, feature, defaultSupports); }; /** * Returns true if the block defines support for a feature, or false otherwise. * * @param {Object} state Data state. * @param {(string|Object)} nameOrType Block name or type object. * @param {string} feature Feature to test. * @param {boolean} defaultSupports Whether feature is supported by * default if not explicitly defined. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const paragraphBlockSupportClassName = useSelect( ( select ) => * select( blocksStore ).hasBlockSupport( 'core/paragraph', 'className' ), * [] * ); * * return ( * <p> * { sprintf( * __( 'core/paragraph supports custom class name?: %s' ), * paragraphBlockSupportClassName * ) } * /p> * ); * }; * ``` * * @return {boolean} Whether block supports feature. */ exports.getBlockSupport = getBlockSupport; function hasBlockSupport(state, nameOrType, feature, defaultSupports) { return !!getBlockSupport(state, nameOrType, feature, defaultSupports); } /** * Normalizes a search term string: removes accents, converts to lowercase, removes extra whitespace. * * @param {string|null|undefined} term Search term to normalize. * @return {string} Normalized search term. */ function getNormalizedSearchTerm(term) { return (0, _removeAccents.default)(term !== null && term !== void 0 ? term : '').toLowerCase().trim(); } /** * Returns true if the block type by the given name or object value matches a * search term, or false otherwise. * * @param {Object} state Blocks state. * @param {(string|Object)} nameOrType Block name or type object. * @param {string} searchTerm Search term by which to filter. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const termFound = useSelect( * ( select ) => * select( blocksStore ).isMatchingSearchTerm( * 'core/navigation', * 'theme' * ), * [] * ); * * return ( * <p> * { sprintf( * __( * 'Search term was found in the title, keywords, category or description in block.json: %s' * ), * termFound * ) } * </p> * ); * }; * ``` * * @return {Object[]} Whether block type matches search term. */ function isMatchingSearchTerm(state, nameOrType, searchTerm = '') { const blockType = getNormalizedBlockType(state, nameOrType); const normalizedSearchTerm = getNormalizedSearchTerm(searchTerm); const isSearchMatch = candidate => getNormalizedSearchTerm(candidate).includes(normalizedSearchTerm); return isSearchMatch(blockType.title) || blockType.keywords?.some(isSearchMatch) || isSearchMatch(blockType.category) || typeof blockType.description === 'string' && isSearchMatch(blockType.description); } /** * Returns a boolean indicating if a block has child blocks or not. * * @param {Object} state Data state. * @param {string} blockName Block type name. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const navigationBlockHasChildBlocks = useSelect( ( select ) => * select( blocksStore ).hasChildBlocks( 'core/navigation' ), * [] * ); * * return ( * <p> * { sprintf( * __( 'core/navigation has child blocks: %s' ), * navigationBlockHasChildBlocks * ) } * </p> * ); * }; * ``` * * @return {boolean} True if a block contains child blocks and false otherwise. */ const hasChildBlocks = (state, blockName) => { return getChildBlockNames(state, blockName).length > 0; }; /** * Returns a boolean indicating if a block has at least one child block with inserter support. * * @param {Object} state Data state. * @param {string} blockName Block type name. * * @example * ```js * import { __, sprintf } from '@wordpress/i18n'; * import { store as blocksStore } from '@wordpress/blocks'; * import { useSelect } from '@wordpress/data'; * * const ExampleComponent = () => { * const navigationBlockHasChildBlocksWithInserterSupport = useSelect( ( select ) => * select( blocksStore ).hasChildBlocksWithInserterSupport( * 'core/navigation' * ), * [] * ); * * return ( * <p> * { sprintf( * __( 'core/navigation has child blocks with inserter support: %s' ), * navigationBlockHasChildBlocksWithInserterSupport * ) } * </p> * ); * }; * ``` * * @return {boolean} True if a block contains at least one child blocks with inserter support * and false otherwise. */ exports.hasChildBlocks = hasChildBlocks; const hasChildBlocksWithInserterSupport = (state, blockName) => { return getChildBlockNames(state, blockName).some(childBlockName => { return hasBlockSupport(state, childBlockName, 'inserter', true); }); }; exports.hasChildBlocksWithInserterSupport = hasChildBlocksWithInserterSupport; const __experimentalHasContentRoleAttribute = (...args) => { (0, _deprecated.default)('__experimentalHasContentRoleAttribute', { since: '6.7', version: '6.8', hint: 'This is a private selector.' }); return (0, _privateSelectors.hasContentRoleAttribute)(...args); }; exports.__experimentalHasContentRoleAttribute = __experimentalHasContentRoleAttribute; //# sourceMappingURL=selectors.js.map