Skip to content

Commit

Permalink
Block Bindings: Create utils to update or remove bindings (WordPress#…
Browse files Browse the repository at this point in the history
…64102)

* Add `useBlockBindingsUtils`

* Use utils in custom fields UI

* Use utils in pattern overrides

* Properly populate custom fields

* Add comments

* Update wrong comment

Co-authored-by: SantosGuillamot <santosguillamot@git.wordpress.org>
Co-authored-by: artemiomorales <artemiosans@git.wordpress.org>
Co-authored-by: cbravobernal <cbravobernal@git.wordpress.org>
  • Loading branch information
4 people authored and bph committed Aug 31, 2024
1 parent 7753eb9 commit 4cfb892
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 113 deletions.
111 changes: 27 additions & 84 deletions packages/block-editor/src/hooks/block-bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
__experimentalVStack as VStack,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
import { useRegistry } from '@wordpress/data';
import { useContext, Fragment } from '@wordpress/element';
import { useViewportMatch } from '@wordpress/compose';

Expand All @@ -24,10 +24,10 @@ import {
canBindAttribute,
getBindableAttributes,
} from '../hooks/use-bindings-attributes';
import { store as blockEditorStore } from '../store';
import { unlock } from '../lock-unlock';
import InspectorControls from '../components/inspector-controls';
import BlockContext from '../components/block-context';
import { useBlockBindingsUtils } from '../utils/block-bindings';

const {
DropdownMenuV2: DropdownMenu,
Expand All @@ -51,17 +51,15 @@ const useToolsPanelDropdownMenuProps = () => {
: {};
};

function BlockBindingsPanelDropdown( {
fieldsList,
addConnection,
attribute,
binding,
} ) {
function BlockBindingsPanelDropdown( { fieldsList, attribute, binding } ) {
const { getBlockBindingsSources } = unlock( blocksPrivateApis );
const registeredSources = getBlockBindingsSources();
const { updateBlockBindings } = useBlockBindingsUtils();
const currentKey = binding?.args?.key;
return (
<>
{ Object.entries( fieldsList ).map( ( [ label, fields ], i ) => (
<Fragment key={ label }>
{ Object.entries( fieldsList ).map( ( [ name, fields ], i ) => (
<Fragment key={ name }>
<DropdownMenuGroup>
{ Object.keys( fieldsList ).length > 1 && (
<Text
Expand All @@ -70,14 +68,19 @@ function BlockBindingsPanelDropdown( {
variant="muted"
aria-hidden
>
{ label }
{ registeredSources[ name ].label }
</Text>
) }
{ Object.entries( fields ).map( ( [ key, value ] ) => (
<DropdownMenuRadioItem
key={ key }
onChange={ () =>
addConnection( key, attribute )
updateBlockBindings( {
[ attribute ]: {
source: name,
args: { key },
},
} )
}
name={ attribute + '-binding' }
value={ key }
Expand Down Expand Up @@ -141,9 +144,8 @@ function EditableBlockBindingsPanelItems( {
attributes,
bindings,
fieldsList,
addConnection,
removeConnection,
} ) {
const { updateBlockBindings } = useBlockBindingsUtils();
const isMobile = useViewportMatch( 'medium', '<' );
return (
<>
Expand All @@ -155,7 +157,9 @@ function EditableBlockBindingsPanelItems( {
hasValue={ () => !! binding }
label={ attribute }
onDeselect={ () => {
removeConnection( attribute );
updateBlockBindings( {
[ attribute ]: undefined,
} );
} }
>
<DropdownMenu
Expand All @@ -175,7 +179,6 @@ function EditableBlockBindingsPanelItems( {
>
<BlockBindingsPanelDropdown
fieldsList={ fieldsList }
addConnection={ addConnection }
attribute={ attribute }
binding={ binding }
/>
Expand All @@ -187,91 +190,33 @@ function EditableBlockBindingsPanelItems( {
);
}

export const BlockBindingsPanel = ( { name, metadata } ) => {
export const BlockBindingsPanel = ( { name: blockName, metadata } ) => {
const registry = useRegistry();
const blockContext = useContext( BlockContext );
const { bindings } = metadata || {};

const bindableAttributes = getBindableAttributes( name );
const { removeAllBlockBindings } = useBlockBindingsUtils();
const bindableAttributes = getBindableAttributes( blockName );
const dropdownMenuProps = useToolsPanelDropdownMenuProps();

const filteredBindings = { ...bindings };
Object.keys( filteredBindings ).forEach( ( key ) => {
if (
! canBindAttribute( name, key ) ||
! canBindAttribute( blockName, key ) ||
filteredBindings[ key ].source === 'core/pattern-overrides'
) {
delete filteredBindings[ key ];
}
} );

const { updateBlockAttributes } = useDispatch( blockEditorStore );

const { _id } = useSelect( ( select ) => {
const { getSelectedBlockClientId } = select( blockEditorStore );

return {
_id: getSelectedBlockClientId(),
};
}, [] );

if ( ! bindableAttributes || bindableAttributes.length === 0 ) {
return null;
}

const removeAllConnections = () => {
const newMetadata = { ...metadata };
delete newMetadata.bindings;
updateBlockAttributes( _id, {
metadata:
Object.keys( newMetadata ).length === 0
? undefined
: newMetadata,
} );
};

const addConnection = ( value, attribute ) => {
// Assuming the block expects a flat structure for its metadata attribute
const newMetadata = {
...metadata,
// Adjust this according to the actual structure expected by your block
bindings: {
...metadata?.bindings,
[ attribute ]: {
source: 'core/post-meta',
args: { key: value },
},
},
};
// Update the block's attributes with the new metadata
updateBlockAttributes( _id, {
metadata: newMetadata,
} );
};

const removeConnection = ( key ) => {
const newMetadata = { ...metadata };
if ( ! newMetadata.bindings ) {
return;
}

delete newMetadata.bindings[ key ];
if ( Object.keys( newMetadata.bindings ).length === 0 ) {
delete newMetadata.bindings;
}
updateBlockAttributes( _id, {
metadata:
Object.keys( newMetadata ).length === 0
? undefined
: newMetadata,
} );
};

const fieldsList = {};
const { getBlockBindingsSources } = unlock( blocksPrivateApis );
const registeredSources = getBlockBindingsSources();
Object.values( registeredSources ).forEach(
( { getFieldsList, label, usesContext } ) => {
Object.entries( registeredSources ).forEach(
( [ sourceName, { getFieldsList, usesContext } ] ) => {
if ( getFieldsList ) {
// Populate context.
const context = {};
Expand All @@ -286,7 +231,7 @@ export const BlockBindingsPanel = ( { name, metadata } ) => {
} );
// Only add source if the list is not empty.
if ( sourceList ) {
fieldsList[ label ] = { ...sourceList };
fieldsList[ sourceName ] = { ...sourceList };
}
}
}
Expand All @@ -312,7 +257,7 @@ export const BlockBindingsPanel = ( { name, metadata } ) => {
<ToolsPanel
label={ __( 'Attributes' ) }
resetAll={ () => {
removeAllConnections();
removeAllBlockBindings();
} }
dropdownMenuProps={ dropdownMenuProps }
className="block-editor-bindings__panel"
Expand All @@ -327,8 +272,6 @@ export const BlockBindingsPanel = ( { name, metadata } ) => {
attributes={ bindableAttributes }
bindings={ filteredBindings }
fieldsList={ fieldsList }
addConnection={ addConnection }
removeConnection={ removeConnection }
/>
) }
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { PrivatePublishDateTimePicker } from './components/publish-date-time-pic
import useSpacingSizes from './components/spacing-sizes-control/hooks/use-spacing-sizes';
import useBlockDisplayTitle from './components/block-title/use-block-display-title';
import TabbedSidebar from './components/tabbed-sidebar';
import { useBlockBindingsUtils } from './utils/block-bindings';

/**
* Private @wordpress/block-editor APIs.
Expand Down Expand Up @@ -92,4 +93,5 @@ lock( privateApis, {
useBlockDisplayTitle,
__unstableBlockStyleVariationOverridesWithConfig,
setBackgroundStyleDefaults,
useBlockBindingsUtils,
} );
98 changes: 98 additions & 0 deletions packages/block-editor/src/utils/block-bindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* WordPress dependencies
*/
import { useDispatch, useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../store';
import { useBlockEditContext } from '../components/block-edit';

export function useBlockBindingsUtils() {
const { clientId } = useBlockEditContext();
const { updateBlockAttributes } = useDispatch( blockEditorStore );
const { getBlockAttributes } = useSelect( blockEditorStore );

/**
* Updates the value of the bindings connected to block attributes.
* It removes the binding when the new value is `undefined`.
*
* @param {Object} bindings Bindings including the attributes to update and the new object.
* @param {string} bindings.source The source name to connect to.
* @param {Object} [bindings.args] Object containing the arguments needed by the source.
*
* @example
* ```js
* import { useBlockBindingsUtils } from '@wordpress/block-editor'
*
* const { updateBlockBindings } = useBlockBindingsUtils();
* updateBlockBindings( {
* url: {
* source: 'core/post-meta',
* args: {
* key: 'url_custom_field',
* },
* },
* alt: {
* source: 'core/post-meta',
* args: {
* key: 'text_custom_field',
* },
* }
* } );
* ```
*/
const updateBlockBindings = ( bindings ) => {
const { metadata } = getBlockAttributes( clientId );
const newBindings = { ...metadata?.bindings };
Object.entries( bindings ).forEach( ( [ attribute, binding ] ) => {
if ( ! binding && newBindings[ attribute ] ) {
delete newBindings[ attribute ];
return;
}
newBindings[ attribute ] = binding;
} );

const newMetadata = {
...metadata,
bindings: newBindings,
};

if ( Object.keys( newMetadata.bindings ).length === 0 ) {
delete newMetadata.bindings;
}

updateBlockAttributes( clientId, {
metadata:
Object.keys( newMetadata ).length === 0
? undefined
: newMetadata,
} );
};

/**
* Removes the bindings property of the `metadata` attribute.
*
* @example
* ```js
* import { useBlockBindingsUtils } from '@wordpress/block-editor'
*
* const { removeAllBlockBindings } = useBlockBindingsUtils();
* removeAllBlockBindings();
* ```
*/
const removeAllBlockBindings = () => {
const { metadata } = getBlockAttributes( clientId );
const newMetadata = { ...metadata };
delete newMetadata.bindings;
updateBlockAttributes( clientId, {
metadata:
Object.keys( newMetadata ).length === 0
? undefined
: newMetadata,
} );
};

return { updateBlockBindings, removeAllBlockBindings };
}
Loading

0 comments on commit 4cfb892

Please sign in to comment.