Skip to content

Commit

Permalink
Rename Group blocks in the Editor via Modal (#53735)
Browse files Browse the repository at this point in the history
* Enable metadata support for Group block

* Enable Rename option in block dropdown

* Add rename functionality via modal

* Use pretty block name as default

* Ensure modal cannot be shown if block does not support naming

* Use aria-disabled

* Use form submit

* Allow flow to reset custom name and remove metadata

* Ensure input has label

* Scaffold initial e2e test

* Test ability to reset value

* Leave options menu in place on modal open

Ensures focus transfer

* Augment tests with assertions on focus management

* Use provided setAttributes

* Extract single component

* Remove redundant comments

* Remove redundant class

* Utilise built in Playwright enabled/disabled

* Use built in assertions

* Use aria- attributes on menu item

* Autofocus input

* Add block rename to block inspector controls

* Move information on saving block data to parent

* Make Rename component simpler

* Use canonical block name as placeholder

* Add aria-described by to Modal

As per #53735 (comment)

* Use custom name in List View aria label

Addresses #53735 (review)

* Add announcement when updating the block name

* Default value should be empty unless custom name is set

* Fix tests

* Enable submitting empty to reset

* Combine e2e tests into single test to save resources

* Rename inspector control to `Block name`

* Remove required and allow submitting true empty values

* Update code to treat empty value as trigger to “reset”

* Centralise assignment of blockTitle

* Add test coverage for List View reflecting custom name

* Update modal title to match menu option
  • Loading branch information
getdave authored Sep 1, 2023
1 parent 959c9c1 commit 0792221
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ $z-layers: (
".edit-site-create-template-part-modal": 1000001,
".block-editor-block-lock-modal": 1000001,
".block-editor-template-part__selection-modal": 1000001,
".block-editor-block-rename-modal": 1000001,

// Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts
// because it uses emotion and not sass. We need it to render on top its parent popover.
Expand Down
4 changes: 3 additions & 1 deletion packages/block-editor/src/components/list-view/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ function ListViewBlock( {
const { toggleBlockHighlight } = useDispatch( blockEditorStore );

const blockInformation = useBlockDisplayInformation( clientId );
const blockTitle = blockInformation?.title || __( 'Untitled' );
const blockTitle =
blockInformation?.name || blockInformation?.title || __( 'Untitled' );

const block = useSelect(
( select ) => select( blockEditorStore ).getBlock( clientId ),
[ clientId ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { store as blockEditorStore } from '../../store';
* @property {WPIcon} icon Block type icon.
* @property {string} description A detailed block type description.
* @property {string} anchor HTML anchor.
* @property {name} name A custom, human readable name for the block.
*/

/**
Expand Down Expand Up @@ -94,6 +95,7 @@ export default function useBlockDisplayInformation( clientId ) {
anchor: attributes?.anchor,
positionLabel,
positionType: attributes?.style?.position?.type,
name: attributes?.metadata?.name,
};
if ( ! match ) return blockTypeInfo;

Expand All @@ -105,6 +107,7 @@ export default function useBlockDisplayInformation( clientId ) {
anchor: attributes?.anchor,
positionLabel,
positionType: attributes?.style?.position?.type,
name: attributes?.metadata?.name,
};
},
[ clientId ]
Expand Down
230 changes: 230 additions & 0 deletions packages/block-editor/src/hooks/block-rename-ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/**
* WordPress dependencies
*/
import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';
import { __, sprintf } from '@wordpress/i18n';
import { getBlockSupport } from '@wordpress/blocks';
import {
MenuItem,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
Button,
TextControl,
Modal,
} from '@wordpress/components';
import { useState } from '@wordpress/element';
import { speak } from '@wordpress/a11y';

/**
* Internal dependencies
*/
import {
BlockSettingsMenuControls,
useBlockDisplayInformation,
InspectorControls,
} from '../components';

const emptyString = ( testString ) => testString?.trim()?.length === 0;

function RenameModal( { blockName, originalBlockName, onClose, onSave } ) {
const [ editedBlockName, setEditedBlockName ] = useState( blockName );

const nameHasChanged = editedBlockName !== blockName;
const nameIsOriginal = editedBlockName === originalBlockName;
const nameIsEmpty = emptyString( editedBlockName );

const isNameValid = nameHasChanged || nameIsOriginal;

const autoSelectInputText = ( event ) => event.target.select();

const dialogDescription = useInstanceId(
RenameModal,
`block-editor-rename-modal__description`
);

const handleSubmit = () => {
// Must be assertive to immediately announce change.
speak(
sprintf(
/* translators: %1$s: type of update (either reset of changed). %2$s: new name/label for the block */
__( 'Block name %1$s to: "%2$s".' ),
nameIsOriginal || nameIsEmpty ? __( 'reset' ) : __( 'changed' ),
editedBlockName
),
'assertive'
);

onSave( editedBlockName );

// Immediate close avoids ability to hit save multiple times.
onClose();
};

return (
<Modal
title={ __( 'Rename' ) }
onRequestClose={ onClose }
overlayClassName="block-editor-block-rename-modal"
aria={ {
describedby: dialogDescription,
} }
>
<p id={ dialogDescription }>
{ __( 'Choose a custom name for this block.' ) }
</p>
<form
onSubmit={ ( e ) => {
e.preventDefault();

if ( ! isNameValid ) {
return;
}

handleSubmit();
} }
>
<VStack spacing="3">
<TextControl
__nextHasNoMarginBottom
value={ editedBlockName }
label={ __( 'Block name' ) }
hideLabelFromVision={ true }
placeholder={ originalBlockName }
onChange={ setEditedBlockName }
onFocus={ autoSelectInputText }
/>
<HStack justify="right">
<Button variant="tertiary" onClick={ onClose }>
{ __( 'Cancel' ) }
</Button>

<Button
aria-disabled={ ! isNameValid }
variant="primary"
type="submit"
>
{ __( 'Save' ) }
</Button>
</HStack>
</VStack>
</form>
</Modal>
);
}

function BlockRenameControl( props ) {
const [ renamingBlock, setRenamingBlock ] = useState( false );

const { clientId, customName, onChange } = props;

const blockInformation = useBlockDisplayInformation( clientId );

return (
<>
<InspectorControls group="advanced">
<TextControl
__nextHasNoMarginBottom
label={ __( 'Block name' ) }
value={ customName || '' }
onChange={ onChange }
/>
</InspectorControls>
<BlockSettingsMenuControls>
{ ( { selectedClientIds } ) => {
// Only enabled for single selections.
const canRename =
selectedClientIds.length === 1 &&
clientId === selectedClientIds[ 0 ];

// This check ensures the `BlockSettingsMenuControls` fill
// doesn't render multiple times and also that it renders for
// the block from which the menu was triggered.
if ( ! canRename ) {
return null;
}

return (
<MenuItem
onClick={ () => {
setRenamingBlock( true );
} }
aria-expanded={ renamingBlock }
aria-haspopup="dialog"
>
{ __( 'Rename' ) }
</MenuItem>
);
} }
</BlockSettingsMenuControls>

{ renamingBlock && (
<RenameModal
blockName={ customName || '' }
originalBlockName={ blockInformation?.title }
onClose={ () => setRenamingBlock( false ) }
onSave={ ( newName ) => {
// If the new value is the block's original name (e.g. `Group`)
// or it is an empty string then assume the intent is to reset
// the value. Therefore reset the metadata.
if (
newName === blockInformation?.title ||
emptyString( newName )
) {
newName = undefined;
}

onChange( newName );
} }
/>
) }
</>
);
}

export const withBlockRenameControl = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const { clientId, name, attributes, setAttributes } = props;

const metaDataSupport = getBlockSupport(
name,
'__experimentalMetadata',
false
);

const supportsBlockNaming = !! (
true === metaDataSupport || metaDataSupport?.name
);

return (
<>
{ supportsBlockNaming && (
<>
<BlockRenameControl
clientId={ clientId }
customName={ attributes?.metadata?.name }
onChange={ ( newName ) => {
setAttributes( {
metadata: {
...( attributes?.metadata &&
attributes?.metadata ),
name: newName,
},
} );
} }
/>
</>
) }

<BlockEdit key="edit" { ...props } />
</>
);
},
'withToolbarControls'
);

addFilter(
'editor.BlockEdit',
'core/block-rename-ui/with-block-rename-control',
withBlockRenameControl
);
3 changes: 3 additions & 0 deletions packages/block-editor/src/hooks/block-rename-ui.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.block-editor-block-rename-modal {
z-index: z-index(".block-editor-block-rename-modal");
}
1 change: 1 addition & 0 deletions packages/block-editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import './metadata-name';
import './behaviors';
import './custom-fields';
import './auto-inserting-blocks';
import './block-rename-ui';

export { useCustomSides } from './dimensions';
export { useLayoutClasses, useLayoutStyles } from './layout';
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
@import "./hooks/padding.scss";
@import "./hooks/position.scss";
@import "./hooks/typography.scss";
@import "./hooks/block-rename-ui.scss";

@import "./components/block-toolbar/style.scss";
@import "./components/inserter/style.scss";
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/group/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"__experimentalOnEnter": true,
"__experimentalOnMerge": true,
"__experimentalSettings": true,
"__experimentalMetadata": true,
"align": [ "wide", "full" ],
"anchor": true,
"ariaLabel": true,
Expand Down
Loading

0 comments on commit 0792221

Please sign in to comment.