@wordpress/block-editor
Version:
190 lines (179 loc) • 6.26 kB
JavaScript
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useContext } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import {
__unstableGetBlockProps as getBlockProps,
getBlockType,
store as blocksStore,
} from '@wordpress/blocks';
import { useMergeRefs, useDisabled } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import warning from '@wordpress/warning';
/**
* Internal dependencies
*/
import useMovingAnimation from '../../use-moving-animation';
import { BlockListBlockContext } from '../block-list-block-context';
import { useFocusFirstElement } from './use-focus-first-element';
import { useIsHovered } from './use-is-hovered';
import { useBlockEditContext } from '../../block-edit/context';
import { useBlockClassNames } from './use-block-class-names';
import { useBlockDefaultClassName } from './use-block-default-class-name';
import { useBlockCustomClassName } from './use-block-custom-class-name';
import { useBlockMovingModeClassNames } from './use-block-moving-mode-class-names';
import { useFocusHandler } from './use-focus-handler';
import { useEventHandlers } from './use-selected-block-event-handlers';
import { useNavModeExit } from './use-nav-mode-exit';
import { useBlockRefProvider } from './use-block-refs';
import { useIntersectionObserver } from './use-intersection-observer';
import { store as blockEditorStore } from '../../../store';
import useBlockOverlayActive from '../../block-content-overlay';
import { unlock } from '../../../lock-unlock';
/**
* If the block count exceeds the threshold, we disable the reordering animation
* to avoid laginess.
*/
const BLOCK_ANIMATION_THRESHOLD = 200;
/**
* This hook is used to lightly mark an element as a block element. The element
* should be the outermost element of a block. Call this hook and pass the
* returned props to the element to mark as a block. If you define a ref for the
* element, it is important to pass the ref to this hook, which the hook in turn
* will pass to the component through the props it returns. Optionally, you can
* also pass any other props through this hook, and they will be merged and
* returned.
*
* @param {Object} props Optional. Props to pass to the element. Must contain
* the ref if one is defined.
* @param {Object} options Options for internal use only.
* @param {boolean} options.__unstableIsHtml
*
* @return {Object} Props to pass to the element to mark as a block.
*/
export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) {
const {
clientId,
className,
wrapperProps = {},
isAligned,
} = useContext( BlockListBlockContext );
const {
index,
mode,
name,
blockApiVersion,
blockTitle,
isPartOfSelection,
adjustScrolling,
enableAnimation,
isSubtreeDisabled,
} = useSelect(
( select ) => {
const {
getBlockAttributes,
getBlockIndex,
getBlockMode,
getBlockName,
isTyping,
getGlobalBlockCount,
isBlockSelected,
isBlockMultiSelected,
isAncestorMultiSelected,
isFirstMultiSelectedBlock,
isBlockSubtreeDisabled,
} = unlock( select( blockEditorStore ) );
const { getActiveBlockVariation } = select( blocksStore );
const isSelected = isBlockSelected( clientId );
const isPartOfMultiSelection =
isBlockMultiSelected( clientId ) ||
isAncestorMultiSelected( clientId );
const blockName = getBlockName( clientId );
const blockType = getBlockType( blockName );
const attributes = getBlockAttributes( clientId );
const match = getActiveBlockVariation( blockName, attributes );
return {
index: getBlockIndex( clientId ),
mode: getBlockMode( clientId ),
name: blockName,
blockApiVersion: blockType?.apiVersion || 1,
blockTitle: match?.title || blockType?.title,
isPartOfSelection: isSelected || isPartOfMultiSelection,
adjustScrolling:
isSelected || isFirstMultiSelectedBlock( clientId ),
enableAnimation:
! isTyping() &&
getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD,
isSubtreeDisabled: isBlockSubtreeDisabled( clientId ),
};
},
[ clientId ]
);
const hasOverlay = useBlockOverlayActive( clientId );
// translators: %s: Type of block (i.e. Text, Image etc)
const blockLabel = sprintf( __( 'Block: %s' ), blockTitle );
const htmlSuffix = mode === 'html' && ! __unstableIsHtml ? '-visual' : '';
const mergedRefs = useMergeRefs( [
props.ref,
useFocusFirstElement( clientId ),
useBlockRefProvider( clientId ),
useFocusHandler( clientId ),
useEventHandlers( clientId ),
useNavModeExit( clientId ),
useIsHovered(),
useIntersectionObserver(),
useMovingAnimation( {
isSelected: isPartOfSelection,
adjustScrolling,
enableAnimation,
triggerAnimationOnChange: index,
} ),
useDisabled( { isDisabled: ! hasOverlay } ),
] );
const blockEditContext = useBlockEditContext();
// Ensures it warns only inside the `edit` implementation for the block.
if ( blockApiVersion < 2 && clientId === blockEditContext.clientId ) {
warning(
`Block type "${ name }" must support API version 2 or higher to work correctly with "useBlockProps" method.`
);
}
return {
tabIndex: 0,
...wrapperProps,
...props,
ref: mergedRefs,
id: `block-${ clientId }${ htmlSuffix }`,
role: 'document',
'aria-label': blockLabel,
'data-block': clientId,
'data-type': name,
'data-title': blockTitle,
inert: isSubtreeDisabled ? 'true' : undefined,
className: classnames(
// The wp-block className is important for editor styles.
classnames( 'block-editor-block-list__block', {
'wp-block': ! isAligned,
'has-block-overlay': hasOverlay,
} ),
className,
props.className,
wrapperProps.className,
useBlockClassNames( clientId ),
useBlockDefaultClassName( clientId ),
useBlockCustomClassName( clientId ),
useBlockMovingModeClassNames( clientId )
),
style: { ...wrapperProps.style, ...props.style },
};
}
/**
* Call within a save function to get the props for the block wrapper.
*
* @param {Object} props Optional. Props to pass to the element.
*/
useBlockProps.save = getBlockProps;