diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 21cd5b2beb7b69..7a0d67f9db0be0 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -132,11 +132,9 @@ _Returns_ ### hasPageContentFocus -Whether or not the editor allows only page content to be edited. - -_Parameters_ +> **Deprecated** -- _state_ `Object`: Global application state. +Whether or not the editor allows only page content to be edited. _Returns_ diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md index 5dbcb095bbf085..4774934651b139 100644 --- a/docs/reference-guides/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -501,6 +501,18 @@ _Related_ - getPreviousBlockClientId in core/block-editor store. +### getRenderingMode + +Returns the post editor's rendering mode. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `string`: Rendering mode. + ### getSelectedBlock _Related_ @@ -1241,6 +1253,19 @@ _Related_ - selectBlock in core/block-editor store. +### setRenderingMode + +Returns an action used to set the rendering mode of the post editor. We support multiple rendering modes: + +- `all`: This is the default mode. It renders the post editor with all the features available. If a template is provided, it's preferred over the post. +- `template-only`: This mode renders the editor with only the template blocks visible. +- `post-only`: This mode extracts the post blocks from the template and renders only those. The idea is to allow the user to edit the post/page in isolation without the wrapping template. +- `template-locked`: This mode renders both the template and the post blocks but the template blocks are locked and can't be edited. The post blocks are editable. + +_Parameters_ + +- _mode_ `string`: Mode (one of 'template-only', 'post-only', 'template-locked' or 'all'). + ### setTemplateValidity _Related_ diff --git a/package-lock.json b/package-lock.json index b2deb50ef559fa..dff3b1f78c595f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56217,7 +56217,7 @@ }, "packages/react-native-aztec": { "name": "@wordpress/react-native-aztec", - "version": "1.108.0", + "version": "1.109.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/element": "file:../element", @@ -56230,7 +56230,7 @@ }, "packages/react-native-bridge": { "name": "@wordpress/react-native-bridge", - "version": "1.108.0", + "version": "1.109.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/react-native-aztec": "file:../react-native-aztec" @@ -56241,7 +56241,7 @@ }, "packages/react-native-editor": { "name": "@wordpress/react-native-editor", - "version": "1.108.0", + "version": "1.109.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/packages/block-editor/src/components/block-rename/index.js b/packages/block-editor/src/components/block-rename/index.js new file mode 100644 index 00000000000000..0379893d412ec9 --- /dev/null +++ b/packages/block-editor/src/components/block-rename/index.js @@ -0,0 +1,3 @@ +export { default as BlockRenameControl } from './rename-control'; +export { default as BlockRenameModal } from './modal'; +export { default as useBlockRename } from './use-block-rename'; diff --git a/packages/block-editor/src/components/block-rename/is-empty-string.js b/packages/block-editor/src/components/block-rename/is-empty-string.js new file mode 100644 index 00000000000000..42d88be77b96e5 --- /dev/null +++ b/packages/block-editor/src/components/block-rename/is-empty-string.js @@ -0,0 +1,3 @@ +export default function isEmptyString( testString ) { + return testString?.trim()?.length === 0; +} diff --git a/packages/block-editor/src/components/block-rename/modal.js b/packages/block-editor/src/components/block-rename/modal.js new file mode 100644 index 00000000000000..a1e9193f348fd0 --- /dev/null +++ b/packages/block-editor/src/components/block-rename/modal.js @@ -0,0 +1,115 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + Button, + TextControl, + Modal, +} from '@wordpress/components'; +import { useInstanceId } from '@wordpress/compose'; +import { __, sprintf } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { speak } from '@wordpress/a11y'; + +/** + * Internal dependencies + */ +import isEmptyString from './is-empty-string'; + +export default function BlockRenameModal( { + blockName, + originalBlockName, + onClose, + onSave, +} ) { + const [ editedBlockName, setEditedBlockName ] = useState( blockName ); + + const nameHasChanged = editedBlockName !== blockName; + const nameIsOriginal = editedBlockName === originalBlockName; + const nameIsEmpty = isEmptyString( editedBlockName ); + + const isNameValid = nameHasChanged || nameIsOriginal; + + const autoSelectInputText = ( event ) => event.target.select(); + + const dialogDescription = useInstanceId( + BlockRenameModal, + `block-editor-rename-modal__description` + ); + + const handleSubmit = () => { + const message = + nameIsOriginal || nameIsEmpty + ? sprintf( + /* translators: %s: new name/label for the block */ + __( 'Block name reset to: "%s".' ), + editedBlockName + ) + : sprintf( + /* translators: %s: new name/label for the block */ + __( 'Block name changed to: "%s".' ), + editedBlockName + ); + + // Must be assertive to immediately announce change. + speak( message, 'assertive' ); + onSave( editedBlockName ); + + // Immediate close avoids ability to hit save multiple times. + onClose(); + }; + + return ( + + + { __( 'Enter a custom name for this block.' ) } + + { + e.preventDefault(); + + if ( ! isNameValid ) { + return; + } + + handleSubmit(); + } } + > + + + + + { __( 'Cancel' ) } + + + + { __( 'Save' ) } + + + + + + ); +} diff --git a/packages/block-editor/src/components/block-rename/rename-control.js b/packages/block-editor/src/components/block-rename/rename-control.js new file mode 100644 index 00000000000000..1f646126d14a4b --- /dev/null +++ b/packages/block-editor/src/components/block-rename/rename-control.js @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { MenuItem } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import { useBlockDisplayInformation } from '..'; +import isEmptyString from './is-empty-string'; +import BlockRenameModal from './modal'; + +export default function BlockRenameControl( { clientId } ) { + const [ renamingBlock, setRenamingBlock ] = useState( false ); + + const { metadata } = useSelect( + ( select ) => { + const { getBlockAttributes } = select( blockEditorStore ); + + const _metadata = getBlockAttributes( clientId )?.metadata; + return { + metadata: _metadata, + }; + }, + [ clientId ] + ); + + const { updateBlockAttributes } = useDispatch( blockEditorStore ); + + const customName = metadata?.name; + + function onChange( newName ) { + updateBlockAttributes( [ clientId ], { + metadata: { + ...( metadata && metadata ), + name: newName, + }, + } ); + } + + const blockInformation = useBlockDisplayInformation( clientId ); + + return ( + <> + { + setRenamingBlock( true ); + } } + aria-expanded={ renamingBlock } + aria-haspopup="dialog" + > + { __( 'Rename' ) } + + { renamingBlock && ( + 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 || + isEmptyString( newName ) + ) { + newName = undefined; + } + + onChange( newName ); + } } + /> + ) } + > + ); +} diff --git a/packages/block-editor/src/hooks/block-rename-ui.scss b/packages/block-editor/src/components/block-rename/style.scss similarity index 100% rename from packages/block-editor/src/hooks/block-rename-ui.scss rename to packages/block-editor/src/components/block-rename/style.scss diff --git a/packages/block-editor/src/components/block-rename/use-block-rename.js b/packages/block-editor/src/components/block-rename/use-block-rename.js new file mode 100644 index 00000000000000..a3fca66f13670e --- /dev/null +++ b/packages/block-editor/src/components/block-rename/use-block-rename.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { getBlockSupport } from '@wordpress/blocks'; + +export default function useBlockRename( name ) { + return { + canRename: getBlockSupport( name, 'renaming', true ), + }; +} diff --git a/packages/block-editor/src/components/block-settings-menu-controls/index.js b/packages/block-editor/src/components/block-settings-menu-controls/index.js index 9063765e72a031..d7a0b001c294da 100644 --- a/packages/block-editor/src/components/block-settings-menu-controls/index.js +++ b/packages/block-editor/src/components/block-settings-menu-controls/index.js @@ -22,6 +22,8 @@ import { BlockLockMenuItem, useBlockLock } from '../block-lock'; import { store as blockEditorStore } from '../../store'; import BlockModeToggle from '../block-settings-menu/block-mode-toggle'; +import { BlockRenameControl, useBlockRename } from '../block-rename'; + const { Fill, Slot } = createSlotFill( 'BlockSettingsMenuControls' ); const BlockSettingsMenuControlsSlot = ( { @@ -44,7 +46,9 @@ const BlockSettingsMenuControlsSlot = ( { ); const { canLock } = useBlockLock( selectedClientIds[ 0 ] ); + const { canRename } = useBlockRename( selectedBlocks[ 0 ] ); const showLockButton = selectedClientIds.length === 1 && canLock; + const showRenameButton = selectedClientIds.length === 1 && canRename; // Check if current selection of blocks is Groupable or Ungroupable // and pass this props down to ConvertToGroupButton. @@ -84,6 +88,11 @@ const BlockSettingsMenuControlsSlot = ( { clientId={ selectedClientIds[ 0 ] } /> ) } + { showRenameButton && ( + + ) } { fills } { fillProps?.canMove && ! fillProps?.onlyBlock && ( 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 = () => { - const message = - nameIsOriginal || nameIsEmpty - ? sprintf( - /* translators: %s: new name/label for the block */ - __( 'Block name reset to: "%s".' ), - editedBlockName - ) - : sprintf( - /* translators: %s: new name/label for the block */ - __( 'Block name changed to: "%s".' ), - editedBlockName - ); - - // Must be assertive to immediately announce change. - speak( message, 'assertive' ); - onSave( editedBlockName ); - - // Immediate close avoids ability to hit save multiple times. - onClose(); - }; - - return ( - - - { __( 'Enter a custom name for this block.' ) } - - { - e.preventDefault(); - - if ( ! isNameValid ) { - return; - } - - handleSubmit(); - } } - > - - - - - { __( 'Cancel' ) } - - - - { __( 'Save' ) } - - - - - - ); -} - -function BlockRenameControl( props ) { - const [ renamingBlock, setRenamingBlock ] = useState( false ); - - const { clientId, customName, onChange } = props; - - const blockInformation = useBlockDisplayInformation( clientId ); - - return ( - <> - - - - - { ( { 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 ( - { - setRenamingBlock( true ); - } } - aria-expanded={ renamingBlock } - aria-haspopup="dialog" - > - { __( 'Rename' ) } - - ); - } } - - - { renamingBlock && ( - 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 withBlockRenameControls = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - const { clientId, name, attributes, setAttributes, isSelected } = props; - - const supportsBlockNaming = hasBlockSupport( name, 'renaming', true ); - - return ( - <> - { isSelected && supportsBlockNaming && ( - <> - { - setAttributes( { - metadata: { - ...attributes?.metadata, - name: newName, - }, - } ); - } } - /> - > - ) } - - - > - ); - }, - 'withBlockRenameControls' -); - -addFilter( - 'editor.BlockEdit', - 'core/block-rename-ui/with-block-rename-controls', - withBlockRenameControls -); diff --git a/packages/block-editor/src/hooks/block-renaming.js b/packages/block-editor/src/hooks/block-renaming.js index 5db06d1a652d41..48e3b801d4eb91 100644 --- a/packages/block-editor/src/hooks/block-renaming.js +++ b/packages/block-editor/src/hooks/block-renaming.js @@ -3,6 +3,15 @@ */ import { addFilter } from '@wordpress/hooks'; import { hasBlockSupport } from '@wordpress/blocks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; +import { TextControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { InspectorControls } from '../components'; +import { useBlockRename } from '../components/block-rename'; /** * Filters registered block settings, adding an `__experimentalLabel` callback if one does not already exist. @@ -38,6 +47,44 @@ export function addLabelCallback( settings ) { return settings; } +export const withBlockRenameControl = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { name, attributes, setAttributes, isSelected } = props; + + const { canRename } = useBlockRename( name ); + + return ( + <> + { isSelected && canRename && ( + + { + setAttributes( { + metadata: { + ...attributes?.metadata, + name: newName, + }, + } ); + } } + /> + + ) } + + > + ); + }, + 'withToolbarControls' +); + +addFilter( + 'editor.BlockEdit', + 'core/block-rename-ui/with-block-rename-control', + withBlockRenameControl +); + addFilter( 'blocks.registerBlockType', 'core/metadata/addLabelCallback', diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 730f0defe0a635..c088216c0645cb 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -22,7 +22,6 @@ import './metadata'; import './custom-fields'; import './block-hooks'; import './block-renaming'; -import './block-rename-ui'; export { useCustomSides } from './dimensions'; export { useLayoutClasses, useLayoutStyles } from './layout'; diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 93ab3b69a7aad3..a55756ae6f53d7 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -15,6 +15,7 @@ @import "./components/block-patterns-paging/style.scss"; @import "./components/block-popover/style.scss"; @import "./components/block-preview/style.scss"; +@import "./components/block-rename/style.scss"; @import "./components/block-settings-menu/style.scss"; @import "./components/block-styles/style.scss"; @import "./components/block-switcher/style.scss"; @@ -56,7 +57,6 @@ @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"; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index f92bd43f806616..0f60b1aaee51ab 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -57,6 +57,7 @@ ### Bug Fix - `Autocomplete`: Add `aria-live` announcements for Mac and IOS Voiceover to fix lack of support for `aria-owns` ([#54902](https://github.com/WordPress/gutenberg/pull/54902)). +- Improve Button saving state accessibility. ([#55547](https://github.com/WordPress/gutenberg/pull/55547)) ### Internal diff --git a/packages/components/src/box-control/stories/index.story.js b/packages/components/src/box-control/stories/index.story.js deleted file mode 100644 index adbd0e15f7c441..00000000000000 --- a/packages/components/src/box-control/stories/index.story.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import BoxControl from '../'; - -export default { - title: 'Components (Experimental)/BoxControl', - component: BoxControl, -}; - -export const _default = () => { - return ; -}; - -const defaultSideValues = { - top: '10px', - right: '10px', - bottom: '10px', - left: '10px', -}; - -function DemoExample( { - sides, - defaultValues = defaultSideValues, - splitOnAxis = false, -} ) { - const [ values, setValues ] = useState( defaultValues ); - - return ( - - ); -} - -export const ArbitrarySides = () => { - return ( - - ); -}; - -export const SingleSide = () => { - return ( - - ); -}; - -export const AxialControls = () => { - return ; -}; - -export const AxialControlsWithSingleSide = () => { - return ( - - ); -}; diff --git a/packages/components/src/box-control/stories/index.story.tsx b/packages/components/src/box-control/stories/index.story.tsx new file mode 100644 index 00000000000000..1b6604048f6d52 --- /dev/null +++ b/packages/components/src/box-control/stories/index.story.tsx @@ -0,0 +1,82 @@ +/** + * External dependencies + */ +import type { Meta, StoryFn } from '@storybook/react'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BoxControl from '../'; + +const meta: Meta< typeof BoxControl > = { + title: 'Components (Experimental)/BoxControl', + component: BoxControl, + argTypes: { + values: { control: { type: null } }, + }, + parameters: { + actions: { argTypesRegex: '^on.*' }, + controls: { expanded: true }, + docs: { canvas: { sourceState: 'shown' } }, + }, +}; +export default meta; + +const TemplateUncontrolled: StoryFn< typeof BoxControl > = ( props ) => { + return ; +}; + +const TemplateControlled: StoryFn< typeof BoxControl > = ( props ) => { + const [ values, setValues ] = useState< ( typeof props )[ 'values' ] >(); + + return ( + { + setValues( nextValue ); + props.onChange?.( nextValue ); + } } + /> + ); +}; + +export const Default = TemplateUncontrolled.bind( {} ); +Default.args = { + label: 'Label', +}; + +export const Controlled = TemplateControlled.bind( {} ); +Controlled.args = { + ...Default.args, +}; + +export const ArbitrarySides = TemplateControlled.bind( {} ); +ArbitrarySides.args = { + ...Default.args, + sides: [ 'top', 'bottom' ], +}; + +export const SingleSide = TemplateControlled.bind( {} ); +SingleSide.args = { + ...Default.args, + sides: [ 'bottom' ], +}; + +export const AxialControls = TemplateControlled.bind( {} ); +AxialControls.args = { + ...Default.args, + splitOnAxis: true, +}; + +export const AxialControlsWithSingleSide = TemplateControlled.bind( {} ); +AxialControlsWithSingleSide.args = { + ...Default.args, + sides: [ 'horizontal' ], + splitOnAxis: true, +}; diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 4b11d9169a0905..0af5144d3d4b7f 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -245,6 +245,11 @@ &.is-secondary.is-busy:disabled, &.is-secondary.is-busy[aria-disabled="true"] { animation: components-button__busy-animation 2500ms infinite linear; + // This should be refactored to use the reduce-motion("animation") mixin + // as soon as https://github.com/WordPress/gutenberg/issues/55566 is closed. + @media (prefers-reduced-motion: reduce) { + animation-duration: 0s; + } opacity: 1; background-size: 100px 100%; // Disable reason: This function call looks nicer when each argument is on its own line. diff --git a/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js b/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js deleted file mode 100644 index 3d65b1ebc98f76..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - findSidebarPanelWithTitle, - openDocumentSettingsSidebar, -} from '@wordpress/e2e-test-utils'; - -describe( 'Custom Taxonomies labels are used', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-custom-taxonomies' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-custom-taxonomies' ); - } ); - - it( 'Ensures the custom taxonomy labels are respected', async () => { - // Open the Setting sidebar. - await openDocumentSettingsSidebar(); - - const openButton = await findSidebarPanelWithTitle( 'Model' ); - expect( openButton ).not.toBeFalsy(); - - // Get the classes from the panel. - const buttonClassName = await ( - await openButton.getProperty( 'className' ) - ).jsonValue(); - - // Open the panel if needed. - if ( -1 === buttonClassName.indexOf( 'is-opened' ) ) { - await openButton.click(); - } - - // Check the add new button. - const labelNew = await page.$x( - "//label[@class='components-form-token-field__label' and contains(text(), 'Add New Model')]" - ); - expect( labelNew ).not.toBeFalsy(); - - // Fill with one entry. - await page.type( - 'input.components-form-token-field__input', - 'Model 1' - ); - await page.keyboard.press( 'Enter' ); - - // Check the "Remove Model" - const value = await page.$x( - "//div[@class='components-form-token-field__input-container']//span//button[@aria-label='Remove Model']" - ); - expect( value ).not.toBeFalsy(); - } ); -} ); diff --git a/packages/edit-site/src/components/dataviews/add-filter.js b/packages/edit-site/src/components/dataviews/add-filter.js index 7192a507b2afe8..7999ff413f96cd 100644 --- a/packages/edit-site/src/components/dataviews/add-filter.js +++ b/packages/edit-site/src/components/dataviews/add-filter.js @@ -16,10 +16,10 @@ import { unlock } from '../../lock-unlock'; import { ENUMERATION_TYPE, OPERATOR_IN } from './constants'; const { - DropdownMenuV2, - DropdownSubMenuV2, - DropdownSubMenuTriggerV2, - DropdownMenuItemV2, + DropdownMenuV2: DropdownMenu, + DropdownSubMenuV2: DropdownSubMenu, + DropdownSubMenuTriggerV2: DropdownSubMenuTrigger, + DropdownMenuItemV2: DropdownMenuItem, } = unlock( componentsPrivateApis ); export default function AddFilter( { fields, view, onChangeView } ) { @@ -48,7 +48,7 @@ export default function AddFilter( { fields, view, onChangeView } ) { } return ( - } > { filter.name } - + } > { filter.elements.map( ( element ) => ( - { onChangeView( ( currentView ) => ( { @@ -98,11 +98,11 @@ export default function AddFilter( { fields, view, onChangeView } ) { role="menuitemcheckbox" > { element.label } - + ) ) } - + ); } ) } - + ); } diff --git a/packages/edit-site/src/components/dataviews/in-filter.js b/packages/edit-site/src/components/dataviews/filter-summary.js similarity index 96% rename from packages/edit-site/src/components/dataviews/in-filter.js rename to packages/edit-site/src/components/dataviews/filter-summary.js index 9dd85eb1eedb26..ae92d0cc462737 100644 --- a/packages/edit-site/src/components/dataviews/in-filter.js +++ b/packages/edit-site/src/components/dataviews/filter-summary.js @@ -20,7 +20,7 @@ const { DropdownMenuCheckboxItemV2: DropdownMenuCheckboxItem, } = unlock( componentsPrivateApis ); -export default ( { filter, view, onChangeView } ) => { +export default function FilterSummary( { filter, view, onChangeView } ) { const filterInView = view.filters.find( ( f ) => f.field === filter.field ); const activeElement = filter.elements.find( ( element ) => element.value === filterInView?.value @@ -76,4 +76,4 @@ export default ( { filter, view, onChangeView } ) => { } ) } ); -}; +} diff --git a/packages/edit-site/src/components/dataviews/filters.js b/packages/edit-site/src/components/dataviews/filters.js index c7681f53fe0ae8..0583fd1e45eb60 100644 --- a/packages/edit-site/src/components/dataviews/filters.js +++ b/packages/edit-site/src/components/dataviews/filters.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { default as InFilter } from './in-filter'; +import FilterSummary from './filter-summary'; import AddFilter from './add-filter'; import ResetFilters from './reset-filters'; import { ENUMERATION_TYPE, OPERATOR_IN } from './constants'; @@ -33,7 +33,7 @@ export default function Filters( { fields, view, onChangeView } ) { } return ( - view.type === v.id ); return ( - { activeView.label } @@ -68,12 +68,12 @@ function ViewTypeMenu( { view, onChangeView, supportedLayouts } ) { } > { __( 'Layout' ) } - + } > { _availableViews.map( ( availableView ) => { return ( - { availableView.label } - + ); } ) } - + ); } const PAGE_SIZE_VALUES = [ 10, 20, 50, 100 ]; function PageSizeMenu( { view, onChangeView } ) { return ( - { view.perPage } @@ -111,12 +111,12 @@ function PageSizeMenu( { view, onChangeView } ) { > { /* TODO: probably label per view type. */ } { __( 'Rows per page' ) } - + } > { PAGE_SIZE_VALUES.map( ( size ) => { return ( - @@ -130,10 +130,10 @@ function PageSizeMenu( { view, onChangeView } ) { role="menuitemcheckbox" > { size } - + ); } ) } - + ); } @@ -145,18 +145,18 @@ function FieldsVisibilityMenu( { view, onChangeView, fields } ) { return null; } return ( - } > { __( 'Fields' ) } - + } > { hidableFields?.map( ( field ) => { return ( - { field.header } - + ); } ) } - + ); } @@ -202,9 +202,9 @@ function SortMenu( { fields, view, onChangeView } ) { ( field ) => field.id === view.sort?.field ); return ( - { currentSortedField?.header } @@ -213,20 +213,20 @@ function SortMenu( { fields, view, onChangeView } ) { } > { __( 'Sort by' ) } - + } > { sortableFields?.map( ( field ) => { const sortedDirection = view.sort?.direction; return ( - } > { field.header } - + } side="left" > @@ -237,7 +237,7 @@ function SortMenu( { fields, view, onChangeView } ) { sortedDirection === direction && field.id === currentSortedField.id; return ( - } suffix={ @@ -264,14 +264,14 @@ function SortMenu( { fields, view, onChangeView } ) { } } > { info.label } - + ); } ) } - + ); } ) } - + ); } @@ -284,7 +284,7 @@ export default function ViewActions( { supportedLayouts, } ) { return ( - } > - + - - + + ); } diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js index 5e102d0f08f3f7..c5d0bd0d340fea 100644 --- a/packages/edit-site/src/components/dataviews/view-list.js +++ b/packages/edit-site/src/components/dataviews/view-list.js @@ -40,12 +40,12 @@ import ItemActions from './item-actions'; import { ENUMERATION_TYPE, OPERATOR_IN } from './constants'; const { - DropdownMenuV2, - DropdownMenuGroupV2, - DropdownMenuItemV2, - DropdownMenuSeparatorV2, - DropdownSubMenuV2, - DropdownSubMenuTriggerV2, + DropdownMenuV2: DropdownMenu, + DropdownMenuGroupV2: DropdownMenuGroup, + DropdownMenuItemV2: DropdownMenuItem, + DropdownMenuSeparatorV2: DropdownMenuSeparator, + DropdownSubMenuV2: DropdownSubMenu, + DropdownSubMenuTriggerV2: DropdownSubMenuTrigger, } = unlock( componentsPrivateApis ); const EMPTY_OBJECT = {}; @@ -80,7 +80,7 @@ function HeaderMenu( { dataView, header } ) { const isFilterable = !! filter; return ( - { isSortable && ( - + { Object.entries( sortingItemsInfo ).map( ( [ direction, info ] ) => ( - } suffix={ @@ -119,13 +119,13 @@ function HeaderMenu( { dataView, header } ) { } } > { info.label } - + ) ) } - + ) } { isHidable && ( - } onSelect={ ( event ) => { event.preventDefault(); @@ -133,21 +133,21 @@ function HeaderMenu( { dataView, header } ) { } } > { __( 'Hide' ) } - + ) } { isFilterable && ( - - + } suffix={ } > { __( 'Filter by' ) } - + } > { filter.elements.map( ( element ) => { @@ -170,7 +170,7 @@ function HeaderMenu( { dataView, header } ) { } return ( - @@ -207,14 +207,14 @@ function HeaderMenu( { dataView, header } ) { } } > { element.label } - + ); } ) } - - + + ) } - + ); } @@ -223,7 +223,7 @@ function WithSeparators( { children } ) { .filter( Boolean ) .map( ( child, i ) => ( - { i > 0 && } + { i > 0 && } { child } ) ); diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 110e891cc1858a..ce12a2aa5fa6b7 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { Notice } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -25,10 +25,11 @@ import { EditorNotices, EditorSnackbars, privateApis as editorPrivateApis, + store as editorStore, } from '@wordpress/editor'; import { __, sprintf } from '@wordpress/i18n'; import { store as coreDataStore } from '@wordpress/core-data'; -import { useMemo } from '@wordpress/element'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -97,14 +98,13 @@ export default function Editor( { listViewToggleElement, isLoading } ) { contextPost, editorMode, canvasMode, + renderingMode, blockEditorMode, isRightSidebarOpen, isInserterOpen, isListViewOpen, showIconLabels, showBlockBreadcrumbs, - hasPageContentFocus, - pageContentFocusType, } = useSelect( ( select ) => { const { getEditedPostContext, @@ -112,12 +112,11 @@ export default function Editor( { listViewToggleElement, isLoading } ) { getCanvasMode, isInserterOpened, isListViewOpened, - hasPageContentFocus: _hasPageContentFocus, - getPageContentFocusType, } = unlock( select( editSiteStore ) ); const { __unstableGetEditorMode } = select( blockEditorStore ); const { getActiveComplementaryArea } = select( interfaceStore ); const { getEntityRecord } = select( coreDataStore ); + const { getRenderingMode } = select( editorStore ); const _context = getEditedPostContext(); // The currently selected entity to display. @@ -133,6 +132,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { : undefined, editorMode: getEditorMode(), canvasMode: getCanvasMode(), + renderingMode: getRenderingMode(), blockEditorMode: __unstableGetEditorMode(), isInserterOpen: isInserterOpened(), isListViewOpen: isListViewOpened(), @@ -147,10 +147,9 @@ export default function Editor( { listViewToggleElement, isLoading } ) { 'core/edit-site', 'showBlockBreadcrumbs' ), - hasPageContentFocus: _hasPageContentFocus(), - pageContentFocusType: getPageContentFocusType(), }; }, [] ); + const { setRenderingMode } = useDispatch( editorStore ); const isViewMode = canvasMode === 'view'; const isEditMode = canvasMode === 'edit'; @@ -165,7 +164,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { const secondarySidebarLabel = isListViewOpen ? __( 'List View' ) : __( 'Block Library' ); - const postWithTemplate = context?.postId; + const postWithTemplate = !! context?.postId; let title; if ( hasLoadedPost ) { @@ -192,31 +191,16 @@ export default function Editor( { listViewToggleElement, isLoading } ) { ! isLoading && ( ( postWithTemplate && !! contextPost && !! editedPost ) || ( ! postWithTemplate && !! editedPost ) ); - const mode = useMemo( () => { - if ( isViewMode ) { - return postWithTemplate ? 'template-locked' : 'all'; - } - - if ( isEditMode && pageContentFocusType === 'hideTemplate' ) { - return 'post-only'; - } - if ( postWithTemplate && hasPageContentFocus ) { - return 'template-locked'; + // This is the only reliable way I've found to reinitialize the rendering mode + // when the canvas mode or the edited entity changes. + useEffect( () => { + if ( canvasMode === 'edit' && postWithTemplate ) { + setRenderingMode( 'template-locked' ); + } else { + setRenderingMode( 'all' ); } - - if ( postWithTemplate && ! hasPageContentFocus ) { - return 'template-only'; - } - - return 'all'; - }, [ - isViewMode, - isEditMode, - postWithTemplate, - pageContentFocusType, - hasPageContentFocus, - ] ); + }, [ canvasMode, postWithTemplate, setRenderingMode ] ); return ( <> @@ -237,7 +221,6 @@ export default function Editor( { listViewToggleElement, isLoading } ) { } settings={ settings } useSubRegistry={ false } - mode={ mode } > { isEditMode && } @@ -298,7 +281,8 @@ export default function Editor( { listViewToggleElement, isLoading } ) { shouldShowBlockBreadcrumbs && ( { - const { - hasPageContentFocus: _hasPageContentFocus, - getEditedPostContext, - } = select( editSiteStore ); + const { getEditedPostContext } = select( editSiteStore ); const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); + const { getRenderingMode } = select( editorStore ); const context = getEditedPostContext(); const queryArgs = [ 'postType', context.postType, context.postId ]; const page = getEditedEntityRecord( ...queryArgs ); return { - hasPageContentFocus: _hasPageContentFocus(), + isEditingPage: + !! context.postId && getRenderingMode() !== 'template-only', hasResolved: hasFinishedResolution( 'getEditedEntityRecord', queryArgs @@ -80,16 +80,16 @@ function PageDocumentActions() { [] ); - const { setHasPageContentFocus } = useDispatch( editSiteStore ); + const { setRenderingMode } = useDispatch( editorStore ); + const [ isAnimated, setIsAnimated ] = useState( false ); + const isLoading = useRef( true ); - const [ hasEditedTemplate, setHasEditedTemplate ] = useState( false ); - const prevHasPageContentFocus = useRef( false ); useEffect( () => { - if ( prevHasPageContentFocus.current && ! hasPageContentFocus ) { - setHasEditedTemplate( true ); + if ( ! isLoading.current ) { + setIsAnimated( true ); } - prevHasPageContentFocus.current = hasPageContentFocus; - }, [ hasPageContentFocus ] ); + isLoading.current = false; + }, [ isEditingPage ] ); if ( ! hasResolved ) { return null; @@ -103,10 +103,10 @@ function PageDocumentActions() { ); } - return hasPageContentFocus ? ( + return isEditingPage ? ( @@ -114,8 +114,10 @@ function PageDocumentActions() { ) : ( setHasPageContentFocus( true ) } + className={ classnames( { + 'is-animated': isAnimated, + } ) } + onBack={ () => setRenderingMode( 'template-locked' ) } /> ); } diff --git a/packages/edit-site/src/components/page-content-focus-notifications/back-to-page-notification.js b/packages/edit-site/src/components/page-content-focus-notifications/back-to-page-notification.js index 9bf9ac33b1d198..7cf963246bed81 100644 --- a/packages/edit-site/src/components/page-content-focus-notifications/back-to-page-notification.js +++ b/packages/edit-site/src/components/page-content-focus-notifications/back-to-page-notification.js @@ -5,6 +5,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { __ } from '@wordpress/i18n'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -25,35 +26,33 @@ export default function BackToPageNotification() { * switches from focusing on editing page content to editing a template. */ export function useBackToPageNotification() { - const hasPageContentFocus = useSelect( - ( select ) => select( editSiteStore ).hasPageContentFocus(), + const renderingMode = useSelect( + ( select ) => select( editorStore ).getRenderingMode(), [] ); const { isPage } = useSelect( editSiteStore ); + const { setRenderingMode } = useDispatch( editorStore ); + const { createInfoNotice } = useDispatch( noticesStore ); const alreadySeen = useRef( false ); - const { createInfoNotice } = useDispatch( noticesStore ); - const { setHasPageContentFocus } = useDispatch( editSiteStore ); - useEffect( () => { - if ( isPage() && ! alreadySeen.current && ! hasPageContentFocus ) { + if ( + isPage() && + ! alreadySeen.current && + renderingMode === 'template-only' + ) { createInfoNotice( __( 'You are editing a template.' ), { isDismissible: true, type: 'snackbar', actions: [ { label: __( 'Back to page' ), - onClick: () => setHasPageContentFocus( true ), + onClick: () => setRenderingMode( 'template-locked' ), }, ], } ); alreadySeen.current = true; } - }, [ - isPage, - hasPageContentFocus, - createInfoNotice, - setHasPageContentFocus, - ] ); + }, [ isPage, renderingMode, createInfoNotice, setRenderingMode ] ); } diff --git a/packages/edit-site/src/components/page-content-focus-notifications/edit-template-notification.js b/packages/edit-site/src/components/page-content-focus-notifications/edit-template-notification.js index 3518bc8c3c51dc..8799eb4d661281 100644 --- a/packages/edit-site/src/components/page-content-focus-notifications/edit-template-notification.js +++ b/packages/edit-site/src/components/page-content-focus-notifications/edit-template-notification.js @@ -6,11 +6,7 @@ import { useEffect, useState, useRef } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { __ } from '@wordpress/i18n'; import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { store as editSiteStore } from '../../store'; +import { store as editorStore } from '@wordpress/editor'; /** * Component that: @@ -27,14 +23,14 @@ import { store as editSiteStore } from '../../store'; * editor iframe canvas. */ export default function EditTemplateNotification( { contentRef } ) { - const hasPageContentFocus = useSelect( - ( select ) => select( editSiteStore ).hasPageContentFocus(), + const renderingMode = useSelect( + ( select ) => select( editorStore ).getRenderingMode(), [] ); const { getNotices } = useSelect( noticesStore ); const { createInfoNotice, removeNotice } = useDispatch( noticesStore ); - const { setHasPageContentFocus } = useDispatch( editSiteStore ); + const { setRenderingMode } = useDispatch( editorStore ); const [ isDialogOpen, setIsDialogOpen ] = useState( false ); @@ -42,7 +38,7 @@ export default function EditTemplateNotification( { contentRef } ) { useEffect( () => { const handleClick = async ( event ) => { - if ( ! hasPageContentFocus ) { + if ( renderingMode === 'template-only' ) { return; } if ( ! event.target.classList.contains( 'is-root-container' ) ) { @@ -62,7 +58,7 @@ export default function EditTemplateNotification( { contentRef } ) { actions: [ { label: __( 'Edit template' ), - onClick: () => setHasPageContentFocus( false ), + onClick: () => setRenderingMode( 'template-only' ), }, ], } @@ -71,7 +67,7 @@ export default function EditTemplateNotification( { contentRef } ) { }; const handleDblClick = ( event ) => { - if ( ! hasPageContentFocus ) { + if ( renderingMode === 'template-only' ) { return; } if ( ! event.target.classList.contains( 'is-root-container' ) ) { @@ -90,7 +86,7 @@ export default function EditTemplateNotification( { contentRef } ) { canvas?.removeEventListener( 'click', handleClick ); canvas?.removeEventListener( 'dblclick', handleDblClick ); }; - }, [ lastNoticeId, hasPageContentFocus, contentRef.current ] ); + }, [ lastNoticeId, renderingMode, contentRef.current ] ); return ( { setIsDialogOpen( false ); - setHasPageContentFocus( false ); + setRenderingMode( 'template-only' ); } } onCancel={ () => setIsDialogOpen( false ) } > diff --git a/packages/edit-site/src/components/page-content-focus-notifications/index.js b/packages/edit-site/src/components/page-content-focus-notifications/index.js index 2b0e636f5231e3..3f76c91eeadeec 100644 --- a/packages/edit-site/src/components/page-content-focus-notifications/index.js +++ b/packages/edit-site/src/components/page-content-focus-notifications/index.js @@ -1,40 +1,10 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; /** * Internal dependencies */ -import { store as editSiteStore } from '../../store'; import EditTemplateNotification from './edit-template-notification'; import BackToPageNotification from './back-to-page-notification'; -import { unlock } from '../../lock-unlock'; export default function PageContentFocusNotifications( { contentRef } ) { - const { pageContentFocusType, canvasMode } = useSelect( ( select ) => { - const { getPageContentFocusType, getCanvasMode } = unlock( - select( editSiteStore ) - ); - const _canvasMode = getCanvasMode(); - return { - canvasMode: _canvasMode, - pageContentFocusType: getPageContentFocusType(), - }; - }, [] ); - const { setPageContentFocusType } = unlock( useDispatch( editSiteStore ) ); - - /* - * Ensure that the page content focus type is set to `disableTemplate` when - * the canvas mode is not `edit`. This makes the experience consistent with - * refreshing the page, which resets the page content focus type. - */ - useEffect( () => { - if ( canvasMode !== 'edit' && !! pageContentFocusType ) { - setPageContentFocusType( null ); - } - }, [ canvasMode, pageContentFocusType, setPageContentFocusType ] ); - return ( <> diff --git a/packages/edit-site/src/components/save-hub/style.scss b/packages/edit-site/src/components/save-hub/style.scss index e864444b2077b4..5cb7cca1f85018 100644 --- a/packages/edit-site/src/components/save-hub/style.scss +++ b/packages/edit-site/src/components/save-hub/style.scss @@ -18,4 +18,11 @@ &[aria-disabled="true"]:hover { color: inherit; } + + &:not(.is-primary) { + &.is-busy, + &.is-busy[aria-disabled="true"]:hover { + color: $gray-900; + } + } } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/index.js b/packages/edit-site/src/components/sidebar-edit-mode/index.js index 8a36a0b5395610..b7683f242a6198 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/index.js @@ -8,6 +8,7 @@ import { useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; import { store as blockEditorStore } from '@wordpress/block-editor'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -33,7 +34,7 @@ export function SidebarComplementaryAreaFills() { isEditorSidebarOpened, hasBlockSelection, supportsGlobalStyles, - hasPageContentFocus, + isEditingPage, } = useSelect( ( select ) => { const _sidebar = select( interfaceStore ).getActiveComplementaryArea( STORE_NAME ); @@ -48,7 +49,9 @@ export function SidebarComplementaryAreaFills() { hasBlockSelection: !! select( blockEditorStore ).getBlockSelectionStart(), supportsGlobalStyles: ! settings?.supportsTemplatePartsMode, - hasPageContentFocus: select( editSiteStore ).hasPageContentFocus(), + isEditingPage: + select( editSiteStore ).isPage() && + select( editorStore ).getRenderingMode() !== 'template-only', }; }, [] ); const { enableComplementaryArea } = useDispatch( interfaceStore ); @@ -60,13 +63,18 @@ export function SidebarComplementaryAreaFills() { return; } if ( hasBlockSelection ) { - if ( ! hasPageContentFocus ) { + if ( ! isEditingPage ) { enableComplementaryArea( STORE_NAME, SIDEBAR_BLOCK ); } } else { enableComplementaryArea( STORE_NAME, SIDEBAR_TEMPLATE ); } - }, [ hasBlockSelection, isEditorSidebarOpened, hasPageContentFocus ] ); + }, [ + hasBlockSelection, + isEditorSidebarOpened, + isEditingPage, + enableComplementaryArea, + ] ); let sidebarName = sidebar; if ( ! isEditorSidebarOpened ) { @@ -85,11 +93,7 @@ export function SidebarComplementaryAreaFills() { > { sidebarName === SIDEBAR_TEMPLATE && ( <> - { hasPageContentFocus ? ( - - ) : ( - - ) } + { isEditingPage ? : } > ) } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js index 4bc81b8c7a2921..58d917baed1f28 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js @@ -8,7 +8,10 @@ import { __ } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; import { check } from '@wordpress/icons'; import { store as blockEditorStore } from '@wordpress/block-editor'; -import { privateApis as editorPrivateApis } from '@wordpress/editor'; +import { + privateApis as editorPrivateApis, + store as editorStore, +} from '@wordpress/editor'; /** * Internal dependencies @@ -31,9 +34,7 @@ export default function EditTemplate() { useSelect( ( select ) => { const { getEditedPostContext, getEditedPostType, getEditedPostId } = select( editSiteStore ); - const { getCanvasMode, getPageContentFocusType } = unlock( - select( editSiteStore ) - ); + const { getRenderingMode } = unlock( select( editorStore ) ); const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); const { __experimentalGetGlobalBlocksByName } = @@ -51,17 +52,12 @@ export default function EditTemplate() { queryArgs ), template: getEditedEntityRecord( ...queryArgs ), - isTemplateHidden: - getCanvasMode() === 'edit' && - getPageContentFocusType() === 'hideTemplate', + isTemplateHidden: getRenderingMode() === 'post-only', postType: _postType, }; }, [] ); - const { setHasPageContentFocus } = useDispatch( editSiteStore ); - // Disable reason: `useDispatch` can't be called conditionally. - // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const { setPageContentFocusType } = unlock( useDispatch( editSiteStore ) ); + const { setRenderingMode } = useDispatch( editorStore ); if ( ! hasResolved ) { return null; @@ -85,7 +81,7 @@ export default function EditTemplate() { { - setHasPageContentFocus( false ); + setRenderingMode( 'template-only' ); onClose(); } } > @@ -102,10 +98,10 @@ export default function EditTemplate() { } isPressed={ ! isTemplateHidden } onClick={ () => { - setPageContentFocusType( + setRenderingMode( isTemplateHidden - ? 'disableTemplate' - : 'hideTemplate' + ? 'template-locked' + : 'post-only' ); } } > diff --git a/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js b/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js index 569bad72ad7ef9..c8ceb089cf0f5d 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js @@ -10,6 +10,7 @@ import { Button } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -20,12 +21,12 @@ import { store as editSiteStore } from '../../../store'; import { POST_TYPE_LABELS, TEMPLATE_POST_TYPE } from '../../../utils/constants'; const SettingsHeader = ( { sidebarName } ) => { - const { hasPageContentFocus, entityType } = useSelect( ( select ) => { - const { getEditedPostType, hasPageContentFocus: _hasPageContentFocus } = - select( editSiteStore ); + const { isEditingPage, entityType } = useSelect( ( select ) => { + const { getEditedPostType, isPage } = select( editSiteStore ); + const { getRenderingMode } = select( editorStore ); return { - hasPageContentFocus: _hasPageContentFocus(), + isEditingPage: isPage() && getRenderingMode() !== 'template-only', entityType: getEditedPostType(), }; } ); @@ -41,7 +42,7 @@ const SettingsHeader = ( { sidebarName } ) => { enableComplementaryArea( STORE_NAME, SIDEBAR_BLOCK ); let templateAriaLabel; - if ( hasPageContentFocus ) { + if ( isEditingPage ) { templateAriaLabel = sidebarName === SIDEBAR_TEMPLATE ? // translators: ARIA label for the Template sidebar tab, selected. @@ -70,11 +71,9 @@ const SettingsHeader = ( { sidebarName } ) => { } ) } aria-label={ templateAriaLabel } - data-label={ - hasPageContentFocus ? __( 'Page' ) : entityLabel - } + data-label={ isEditingPage ? __( 'Page' ) : entityLabel } > - { hasPageContentFocus ? __( 'Page' ) : entityLabel } + { isEditingPage ? __( 'Page' ) : entityLabel } diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index b6931b8e656653..19508f0a59f8ea 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -7,13 +7,10 @@ import classnames from 'classnames'; * WordPress dependencies */ import { - __unstableComposite as Composite, - __unstableUseCompositeState as useCompositeState, - __unstableCompositeItem as CompositeItem, Disabled, TabPanel, + privateApis as componentsPrivateApis, } from '@wordpress/components'; - import { __, sprintf } from '@wordpress/i18n'; import { getCategories, @@ -43,6 +40,12 @@ const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock( blockEditorPrivateApis ); +const { + CompositeV2: Composite, + CompositeItemV2: CompositeItem, + useCompositeStoreV2: useCompositeStore, +} = unlock( componentsPrivateApis ); + // The content area of the Style Book is rendered within an iframe so that global styles // are applied to elements within the entire content area. To support elements that are // not part of the block previews, such as headings and layout for the block previews, @@ -66,6 +69,8 @@ const STYLE_BOOK_IFRAME_STYLES = ` padding: 16px; width: 100%; box-sizing: border-box; + scroll-margin-top: 32px; + scroll-margin-bottom: 32px; } .edit-site-style-book__example.is-selected { @@ -332,6 +337,7 @@ const StyleBookBody = ( { } isSelected={ isSelected } onSelect={ onSelect } + key={ category } /> ); @@ -339,12 +345,14 @@ const StyleBookBody = ( { const Examples = memo( ( { className, examples, category, label, isSelected, onSelect } ) => { - const composite = useCompositeState( { orientation: 'vertical' } ); + const compositeStore = useCompositeStore( { orientation: 'vertical' } ); + return ( { examples .filter( ( example ) => @@ -354,7 +362,6 @@ const Examples = memo( { +const Example = ( { id, title, blocks, isSelected, onClick } ) => { const originalSettings = useSelect( ( select ) => select( blockEditorStore ).getSettings(), [] @@ -385,35 +392,41 @@ const Example = ( { composite, id, title, blocks, isSelected, onClick } ) => { ); return ( - - - { title } - - - - + + } + role="button" + onClick={ onClick } + > + + { title } + + - - - + + + + + + + - + ); }; diff --git a/packages/edit-site/src/components/welcome-guide/page.js b/packages/edit-site/src/components/welcome-guide/page.js index adb64a8033e999..db89d9b653ad58 100644 --- a/packages/edit-site/src/components/welcome-guide/page.js +++ b/packages/edit-site/src/components/welcome-guide/page.js @@ -23,8 +23,8 @@ export default function WelcomeGuidePage() { 'core/edit-site', 'welcomeGuide' ); - const { hasPageContentFocus } = select( editSiteStore ); - return isPageActive && ! isEditorActive && hasPageContentFocus(); + const { isPage } = select( editSiteStore ); + return isPageActive && ! isEditorActive && isPage(); }, [] ); if ( ! isVisible ) { diff --git a/packages/edit-site/src/components/welcome-guide/template.js b/packages/edit-site/src/components/welcome-guide/template.js index f0c02c09d1124a..073a19c2d6efdc 100644 --- a/packages/edit-site/src/components/welcome-guide/template.js +++ b/packages/edit-site/src/components/welcome-guide/template.js @@ -5,6 +5,7 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { Guide } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -23,12 +24,13 @@ export default function WelcomeGuideTemplate() { 'core/edit-site', 'welcomeGuide' ); - const { isPage, hasPageContentFocus } = select( editSiteStore ); + const { isPage } = select( editSiteStore ); + const { getRenderingMode } = select( editorStore ); return ( isTemplateActive && ! isEditorActive && isPage() && - ! hasPageContentFocus() + getRenderingMode() === 'template-only' ); }, [] ); diff --git a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js index 37baef18dffd48..ece6d349db1e7f 100644 --- a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js +++ b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js @@ -24,6 +24,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as interfaceStore } from '@wordpress/interface'; import { store as noticesStore } from '@wordpress/notices'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -42,15 +43,18 @@ const { useHistory } = unlock( routerPrivateApis ); function usePageContentFocusCommands() { const { record: template } = useEditedEntityRecord(); - const { isPage, canvasMode, hasPageContentFocus } = useSelect( - ( select ) => ( { - isPage: select( editSiteStore ).isPage(), - canvasMode: unlock( select( editSiteStore ) ).getCanvasMode(), - hasPageContentFocus: select( editSiteStore ).hasPageContentFocus(), - } ), - [] - ); - const { setHasPageContentFocus } = useDispatch( editSiteStore ); + const { isPage, canvasMode, renderingMode } = useSelect( ( select ) => { + const { isPage: _isPage, getCanvasMode } = unlock( + select( editSiteStore ) + ); + const { getRenderingMode } = select( editorStore ); + return { + isPage: _isPage(), + canvasMode: getCanvasMode(), + renderingMode: getRenderingMode(), + }; + }, [] ); + const { setRenderingMode } = useDispatch( editorStore ); if ( ! isPage || canvasMode !== 'edit' ) { return { isLoading: false, commands: [] }; @@ -58,7 +62,7 @@ function usePageContentFocusCommands() { const commands = []; - if ( hasPageContentFocus ) { + if ( renderingMode !== 'template-only' ) { commands.push( { name: 'core/switch-to-template-focus', /* translators: %1$s: template title */ @@ -68,7 +72,7 @@ function usePageContentFocusCommands() { ), icon: layout, callback: ( { close } ) => { - setHasPageContentFocus( false ); + setRenderingMode( 'template-only' ); close(); }, } ); @@ -78,7 +82,7 @@ function usePageContentFocusCommands() { label: __( 'Back to page' ), icon: page, callback: ( { close } ) => { - setHasPageContentFocus( true ); + setRenderingMode( 'template-locked' ); close(); }, } ); @@ -122,8 +126,10 @@ function useManipulateDocumentCommands() { const { isLoaded, record: template } = useEditedEntityRecord(); const { removeTemplate, revertTemplate } = useDispatch( editSiteStore ); const history = useHistory(); - const hasPageContentFocus = useSelect( - ( select ) => select( editSiteStore ).hasPageContentFocus(), + const isEditingPage = useSelect( + ( select ) => + select( editSiteStore ).isPage() && + select( editorStore ).getRenderingMode() !== 'template-only', [] ); @@ -133,7 +139,7 @@ function useManipulateDocumentCommands() { const commands = []; - if ( isTemplateRevertable( template ) && ! hasPageContentFocus ) { + if ( isTemplateRevertable( template ) && ! isEditingPage ) { const label = template.type === TEMPLATE_POST_TYPE ? /* translators: %1$s: template title */ @@ -157,7 +163,7 @@ function useManipulateDocumentCommands() { } ); } - if ( isTemplateRemovable( template ) && ! hasPageContentFocus ) { + if ( isTemplateRemovable( template ) && ! isEditingPage ) { const label = template.type === TEMPLATE_POST_TYPE ? /* translators: %1$s: template title */ diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 000a5f71bbbf0e..2dd7aacd384014 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -575,6 +575,10 @@ export const switchEditorMode = export const setHasPageContentFocus = ( hasPageContentFocus ) => ( { dispatch, registry } ) => { + deprecated( `dispatch( 'core/edit-site' ).setHasPageContentFocus`, { + since: '6.5', + } ); + if ( hasPageContentFocus ) { registry.dispatch( blockEditorStore ).clearSelectedBlock(); } diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index 3e2bfe2ee47b24..2d858c15208991 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -11,7 +11,7 @@ import { store as preferencesStore } from '@wordpress/preferences'; */ export const setCanvasMode = ( mode ) => - ( { registry, dispatch, select } ) => { + ( { registry, dispatch } ) => { registry.dispatch( blockEditorStore ).__unstableSetEditorMode( 'edit' ); dispatch( { type: 'SET_CANVAS_MODE', @@ -30,10 +30,6 @@ export const setCanvasMode = ) { dispatch.setIsListViewOpened( true ); } - // Switch focus away from editing the template when switching to view mode. - if ( mode === 'view' && select.isPage() ) { - dispatch.setHasPageContentFocus( true ); - } }; /** @@ -49,22 +45,3 @@ export const setEditorCanvasContainerView = view, } ); }; - -/** - * Sets the type of page content focus. Can be one of: - * - * - `'disableTemplate'`: Disable the blocks belonging to the page's template. - * - `'hideTemplate'`: Hide the blocks belonging to the page's template. - * - * @param {'disableTemplate'|'hideTemplate'} pageContentFocusType The type of page content focus. - * - * @return {Object} Action object. - */ -export const setPageContentFocusType = - ( pageContentFocusType ) => - ( { dispatch } ) => { - dispatch( { - type: 'SET_PAGE_CONTENT_FOCUS_TYPE', - pageContentFocusType, - } ); - }; diff --git a/packages/edit-site/src/store/private-selectors.js b/packages/edit-site/src/store/private-selectors.js index 0d4cf2b3eefdaa..1f1f6e999fdb29 100644 --- a/packages/edit-site/src/store/private-selectors.js +++ b/packages/edit-site/src/store/private-selectors.js @@ -1,8 +1,3 @@ -/** - * Internal dependencies - */ -import { hasPageContentFocus } from './selectors'; - /** * Returns the current canvas mode. * @@ -24,20 +19,3 @@ export function getCanvasMode( state ) { export function getEditorCanvasContainerView( state ) { return state.editorCanvasContainerView; } - -/** - * Returns the type of the current page content focus, or null if there is no - * page content focus. - * - * Possible values are: - * - * - `'disableTemplate'`: Disable the blocks belonging to the page's template. - * - `'hideTemplate'`: Hide the blocks belonging to the page's template. - * - * @param {Object} state Global application state. - * - * @return {'disableTemplate'|'hideTemplate'|null} Type of the current page content focus. - */ -export function getPageContentFocusType( state ) { - return hasPageContentFocus( state ) ? state.pageContentFocusType : null; -} diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index e99c6dda1fc1d0..a46d215f905074 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -157,43 +157,6 @@ function editorCanvasContainerView( state = undefined, action ) { return state; } -/** - * Reducer used to track whether the editor allows only page content to be - * edited. - * - * @param {boolean} state Current state. - * @param {Object} action Dispatched action. - * - * @return {boolean} Updated state. - */ -export function hasPageContentFocus( state = false, action ) { - switch ( action.type ) { - case 'SET_EDITED_POST': - return !! action.context?.postId; - case 'SET_HAS_PAGE_CONTENT_FOCUS': - return action.hasPageContentFocus; - } - - return state; -} - -/** - * Reducer used to track the type of page content focus. - * - * @param {string} state Current state. - * @param {Object} action Dispatched action. - * - * @return {string} Updated state. - */ -export function pageContentFocusType( state = 'disableTemplate', action ) { - switch ( action.type ) { - case 'SET_PAGE_CONTENT_FOCUS_TYPE': - return action.pageContentFocusType; - } - - return state; -} - export default combineReducers( { deviceType, settings, @@ -203,6 +166,4 @@ export default combineReducers( { saveViewPanel, canvasMode, editorCanvasContainerView, - hasPageContentFocus, - pageContentFocusType, } ); diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js index f9c2f7d65cfaf4..9d00e141270c40 100644 --- a/packages/edit-site/src/store/selectors.js +++ b/packages/edit-site/src/store/selectors.js @@ -7,6 +7,7 @@ import deprecated from '@wordpress/deprecated'; import { Platform } from '@wordpress/element'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as blockEditorStore } from '@wordpress/block-editor'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -181,7 +182,10 @@ export const __experimentalGetInsertionPoint = createRegistrySelector( return { rootClientId, insertionIndex, filterValue }; } - if ( hasPageContentFocus( state ) ) { + if ( + isPage( state ) && + select( editorStore ).getRenderingMode() !== 'template-only' + ) { const [ postContentClientId ] = select( blockEditorStore ).__experimentalGetGlobalBlocksByName( 'core/post-content' @@ -310,10 +314,14 @@ export function isPage( state ) { /** * Whether or not the editor allows only page content to be edited. * - * @param {Object} state Global application state. + * @deprecated * * @return {boolean} Whether or not focus is on editing page content. */ -export function hasPageContentFocus( state ) { - return isPage( state ) ? state.hasPageContentFocus : false; +export function hasPageContentFocus() { + deprecated( `select( 'core/edit-site' ).hasPageContentFocus`, { + since: '6.5', + } ); + + return false; } diff --git a/packages/edit-site/src/store/test/actions.js b/packages/edit-site/src/store/test/actions.js index 3eb1855e68e9d7..6f0597fec12434 100644 --- a/packages/edit-site/src/store/test/actions.js +++ b/packages/edit-site/src/store/test/actions.js @@ -7,18 +7,19 @@ import { createRegistry } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; import { store as noticesStore } from '@wordpress/notices'; import { store as preferencesStore } from '@wordpress/preferences'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies */ import { store as editSiteStore } from '..'; -import { setHasPageContentFocus } from '../actions'; function createRegistryWithStores() { // create a registry const registry = createRegistry(); // register stores + registry.register( editorStore ); registry.register( blockEditorStore ); registry.register( coreStore ); registry.register( editSiteStore ); @@ -177,34 +178,4 @@ describe( 'actions', () => { ).toBe( true ); } ); } ); - - describe( 'setHasPageContentFocus', () => { - it( 'toggles the page content lock on', () => { - const dispatch = jest.fn(); - const clearSelectedBlock = jest.fn(); - const registry = { - dispatch: () => ( { clearSelectedBlock } ), - }; - setHasPageContentFocus( true )( { dispatch, registry } ); - expect( clearSelectedBlock ).toHaveBeenCalled(); - expect( dispatch ).toHaveBeenCalledWith( { - type: 'SET_HAS_PAGE_CONTENT_FOCUS', - hasPageContentFocus: true, - } ); - } ); - - it( 'toggles the page content lock off', () => { - const dispatch = jest.fn(); - const clearSelectedBlock = jest.fn(); - const registry = { - dispatch: () => ( { clearSelectedBlock } ), - }; - setHasPageContentFocus( false )( { dispatch, registry } ); - expect( clearSelectedBlock ).not.toHaveBeenCalled(); - expect( dispatch ).toHaveBeenCalledWith( { - type: 'SET_HAS_PAGE_CONTENT_FOCUS', - hasPageContentFocus: false, - } ); - } ); - } ); } ); diff --git a/packages/edit-site/src/store/test/reducer.js b/packages/edit-site/src/store/test/reducer.js index a5e47ec5bbbaf3..f39261fea38802 100644 --- a/packages/edit-site/src/store/test/reducer.js +++ b/packages/edit-site/src/store/test/reducer.js @@ -11,8 +11,6 @@ import { editedPost, blockInserterPanel, listViewPanel, - hasPageContentFocus, - pageContentFocusType, } from '../reducer'; import { setIsInserterOpened } from '../actions'; @@ -149,64 +147,4 @@ describe( 'state', () => { ); } ); } ); - - describe( 'hasPageContentFocus()', () => { - it( 'defaults to false', () => { - expect( hasPageContentFocus( undefined, {} ) ).toBe( false ); - } ); - - it( 'becomes false when editing a template', () => { - expect( - hasPageContentFocus( true, { - type: 'SET_EDITED_POST', - postType: 'wp_template', - } ) - ).toBe( false ); - } ); - - it( 'becomes true when editing a page', () => { - expect( - hasPageContentFocus( false, { - type: 'SET_EDITED_POST', - postType: 'wp_template', - context: { - postType: 'page', - postId: 123, - }, - } ) - ).toBe( true ); - } ); - - it( 'can be set', () => { - expect( - hasPageContentFocus( false, { - type: 'SET_HAS_PAGE_CONTENT_FOCUS', - hasPageContentFocus: true, - } ) - ).toBe( true ); - expect( - hasPageContentFocus( true, { - type: 'SET_HAS_PAGE_CONTENT_FOCUS', - hasPageContentFocus: false, - } ) - ).toBe( false ); - } ); - } ); - - describe( 'pageContentFocusType', () => { - it( 'defaults to disableTemplate', () => { - expect( pageContentFocusType( undefined, {} ) ).toBe( - 'disableTemplate' - ); - } ); - - it( 'can be set', () => { - expect( - pageContentFocusType( 'disableTemplate', { - type: 'SET_PAGE_CONTENT_FOCUS_TYPE', - pageContentFocusType: 'enableTemplate', - } ) - ).toBe( 'enableTemplate' ); - } ); - } ); } ); diff --git a/packages/edit-site/src/store/test/selectors.js b/packages/edit-site/src/store/test/selectors.js index 7e36d2f4b75f4d..07577e897b04ec 100644 --- a/packages/edit-site/src/store/test/selectors.js +++ b/packages/edit-site/src/store/test/selectors.js @@ -13,7 +13,6 @@ import { isInserterOpened, isListViewOpened, isPage, - hasPageContentFocus, } from '../selectors'; describe( 'selectors', () => { @@ -88,38 +87,4 @@ describe( 'selectors', () => { expect( isPage( state ) ).toBe( false ); } ); } ); - - describe( 'hasPageContentFocus', () => { - it( 'returns true if locked and the edited post type is a page', () => { - const state = { - editedPost: { - postType: 'wp_template', - context: { postType: 'page', postId: 123 }, - }, - hasPageContentFocus: true, - }; - expect( hasPageContentFocus( state ) ).toBe( true ); - } ); - - it( 'returns false if not locked and the edited post type is a page', () => { - const state = { - editedPost: { - postType: 'wp_template', - context: { postType: 'page', postId: 123 }, - }, - hasPageContentFocus: false, - }; - expect( hasPageContentFocus( state ) ).toBe( false ); - } ); - - it( 'returns false if locked and the edited post type is a template', () => { - const state = { - editedPost: { - postType: 'wp_template', - }, - hasPageContentFocus: true, - }; - expect( hasPageContentFocus( state ) ).toBe( false ); - } ); - } ); } ); diff --git a/packages/edit-site/src/utils/constants.js b/packages/edit-site/src/utils/constants.js index 2f00bc13f6de8d..0b92252935a79f 100644 --- a/packages/edit-site/src/utils/constants.js +++ b/packages/edit-site/src/utils/constants.js @@ -40,7 +40,7 @@ export const FOCUSABLE_ENTITIES = [ /** * Block types that are considered to be page content. These are the only blocks - * editable when hasPageContentFocus() is true. + * editable when the page is focused. */ export const PAGE_CONTENT_BLOCK_TYPES = { 'core/post-title': true, diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js index 57c46ab3013385..c3bbad22682762 100644 --- a/packages/editor/src/components/post-saved-state/index.js +++ b/packages/editor/src/components/post-saved-state/index.js @@ -9,6 +9,7 @@ import classnames from 'classnames'; import { __unstableGetAnimateClassName as getAnimateClassName, Button, + Tooltip, } from '@wordpress/components'; import { usePrevious, useViewportMatch } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -128,45 +129,53 @@ export default function PostSavedState( { text = shortLabel; } + const buttonAccessibleLabel = text || label; + + /** + * The tooltip needs to be enabled only if the button is not disabled. When + * relying on the internal Button tooltip functionality, this causes the + * resulting `button` element to be always removed and re-added to the DOM, + * causing focus loss. An alternative approach to circumvent the issue + * is not to use the `label` and `shortcut` props on `Button` (which would + * trigger the tooltip), and instead manually wrap the `Button` in a separate + * `Tooltip` component. + */ + const tooltipProps = isDisabled + ? undefined + : { + text: buttonAccessibleLabel, + shortcut: displayShortcut.primary( 's' ), + }; + // Use common Button instance for all saved states so that focus is not // lost. return ( - savePost() } - /* - * We want the tooltip to show the keyboard shortcut only when the - * button does something, i.e. when it's not disabled. - */ - shortcut={ isDisabled ? undefined : displayShortcut.primary( 's' ) } - /* - * Displaying the keyboard shortcut conditionally makes the tooltip - * itself show conditionally. This would trigger a full-rerendering - * of the button that we want to avoid. By setting `showTooltip`, - * the tooltip is always rendered even when there's no keyboard shortcut. - */ - showTooltip - variant="tertiary" - icon={ isLargeViewport ? undefined : cloudUpload } - // Make sure the aria-label has always a value, as the default `text` is undefined on small screens. - label={ text || label } - aria-disabled={ isDisabled } - > - { isSavedState && } - { text } - + + + { isSavedState && } + { text } + + ); } diff --git a/packages/editor/src/components/provider/README.md b/packages/editor/src/components/provider/README.md index f2b9b697a09083..deaa9375bba746 100644 --- a/packages/editor/src/components/provider/README.md +++ b/packages/editor/src/components/provider/README.md @@ -22,19 +22,6 @@ The post object to edit The template object wrapper the edited post. This is optional and can only be used when the post type supports templates (like posts and pages). -### `mode` - -- **Type:** `String` -- **Required** `no` -- **default** `all` - -This is the rendering mode of the post editor. We support multiple rendering modes: - -- `all`: This is the default mode. It renders the post editor with all the features available. If a template is provided, it's preferred over the post. -- `template-only`: This mode renders the editor with only the template blocks visible. -- `post-only`: This mode extracts the post blocks from the template and renders only those. The idea is to allow the user to edit the post/page in isolation without the wrapping template. -- `template-locked`: This mode renders both the template and the post blocks but the template blocks are locked and can't be edited. The post blocks are editable. - ### `settings` - **Type:** `Object` diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 75f1769927715f..37128918f1a422 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -178,7 +178,6 @@ function useBlockEditorProps( post, template, mode ) { export const ExperimentalEditorProvider = withRegistryProvider( ( { - mode = 'all', post, settings, recovery, @@ -187,6 +186,10 @@ export const ExperimentalEditorProvider = withRegistryProvider( BlockEditorProviderComponent = ExperimentalBlockEditorProvider, __unstableTemplate: template, } ) => { + const mode = useSelect( + ( select ) => select( editorStore ).getRenderingMode(), + [] + ); const shouldRenderTemplate = !! template && mode !== 'post-only'; const rootLevelPost = shouldRenderTemplate ? template : post; const defaultBlockContext = useMemo( () => { diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 1cca9ee05ee30e..0c946d4124f49f 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -548,6 +548,27 @@ export function updateEditorSettings( settings ) { }; } +/** + * Returns an action used to set the rendering mode of the post editor. We support multiple rendering modes: + * + * - `all`: This is the default mode. It renders the post editor with all the features available. If a template is provided, it's preferred over the post. + * - `template-only`: This mode renders the editor with only the template blocks visible. + * - `post-only`: This mode extracts the post blocks from the template and renders only those. The idea is to allow the user to edit the post/page in isolation without the wrapping template. + * - `template-locked`: This mode renders both the template and the post blocks but the template blocks are locked and can't be edited. The post blocks are editable. + * + * @param {string} mode Mode (one of 'template-only', 'post-only', 'template-locked' or 'all'). + */ +export const setRenderingMode = + ( mode ) => + ( { dispatch, registry } ) => { + registry.dispatch( blockEditorStore ).clearSelectedBlock(); + + dispatch( { + type: 'SET_RENDERING_MODE', + mode, + } ); + }; + /** * Backward compatibility */ diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 647e0158660040..48356fd8e99e3c 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -279,6 +279,15 @@ export function editorSettings( state = EDITOR_SETTINGS_DEFAULTS, action ) { return state; } +export function renderingMode( state = 'all', action ) { + switch ( action.type ) { + case 'SET_RENDERING_MODE': + return action.mode; + } + + return state; +} + export default combineReducers( { postId, postType, @@ -290,4 +299,5 @@ export default combineReducers( { isReady, editorSettings, postAutosavingLock, + renderingMode, } ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index a2bbf0f47770f1..78944335bd3981 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1188,6 +1188,17 @@ export function getEditorSettings( state ) { return state.editorSettings; } +/** + * Returns the post editor's rendering mode. + * + * @param {Object} state Editor state. + * + * @return {string} Rendering mode. + */ +export function getRenderingMode( state ) { + return state.renderingMode; +} + /* * Backward compatibility */ diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 996eeabaae1c10..631781600d78d8 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.108.0", + "version": "1.109.0", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 927b2ef14ec3d7..20ae851c89686b 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.108.0", + "version": "1.109.0", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 2fef3d50be3389..635937c4d8ce0b 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -10,7 +10,10 @@ For each user feature we should also add a importance categorization label to i --> ## Unreleased + +## 1.109.0 - [*] Audio block: Improve legibility of audio file details on various background colors [#55627] +- [*] In the deeply nested block warning, only display the ungroup option for blocks that support it [#56445] ## 1.108.0 - [*] Fix error when pasting deeply nested structure content [#55613] diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 95decffb6206d4..d6f0ca39a09bf2 100644 --- a/packages/react-native-editor/ios/Podfile.lock +++ b/packages/react-native-editor/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - ReactCommon/turbomodule/core (= 0.71.11) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.108.0): + - Gutenberg (1.109.0): - React-Core (= 0.71.11) - React-CoreModules (= 0.71.11) - React-RCTImage (= 0.71.11) @@ -429,7 +429,7 @@ PODS: - React-RCTImage - RNSVG (13.9.0): - React-Core - - RNTAztecView (1.108.0): + - RNTAztecView (1.109.0): - React-Core - WordPress-Aztec-iOS (= 1.19.9) - SDWebImage (5.11.1): @@ -617,7 +617,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: f07662560742d82a5b73cee116c70b0b49bcc220 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - Gutenberg: c765216d64630eb86d053ff53214abaedbd5f535 + Gutenberg: dd556a8be3f8b5225862823f050e57d0a22e0614 hermes-engine: 34c863b446d0135b85a6536fa5fd89f48196f848 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c @@ -662,7 +662,7 @@ SPEC CHECKSUMS: RNReanimated: d4f363f4987ae0ade3e36ff81c94e68261bf4b8d RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789 RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 - RNTAztecView: dfa8817995fc92b8a0ba755763aab77445650c91 + RNTAztecView: 8415d8e322e98d087b3f8fbba0669e84d6b235cb SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d WordPress-Aztec-iOS: fbebd569c61baa252b3f5058c0a2a9a6ada686bb diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index e00754eccec441..b599c2cc51c65a 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-editor", - "version": "1.108.0", + "version": "1.109.0", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/platform-docs/docs/basic-concepts/settings.md b/platform-docs/docs/basic-concepts/settings.md index a2d376e71a29dd..955f9ec31ad3f0 100644 --- a/platform-docs/docs/basic-concepts/settings.md +++ b/platform-docs/docs/basic-concepts/settings.md @@ -6,14 +6,43 @@ sidebar_position: 3 You can customize the block editor by providing a `settings` prop to the `BlockEditorProvider` component. This prop accepts an object with the following properties: +## __experimentalFeatures + +The experimental features setting is an object that allows you to enable/disable common block editor features. For instance, the following settings enables support for text, background colors and allow users to pick a custom color or one of the defined theme palette colors. Core block types and third-party block types using the block supports feature will automatically take these settings into account. + +```js +import { BlockEditorProvider, BlockCanvas } from '@wordpress/block-editor'; + +const features = { + color: { + custom: true, + text: true, + background: true, + palette: { + theme: [ + { name: 'red', color: '#f00', slug: 'red' }, + { name: 'white', color: '#fff', slug: 'white' }, + { name: 'blue', color: '#00f', slug: 'blue' }, + ], + }, + }, +}; + +export default function App() { + return ( + + + + ); +} +``` + ## styles The styles setting is an array of editor styles to enqueue in the iframe/canvas of the block editor. Each style is an object with a `css` property. Example: ```jsx -import { BlockEditorProvider, BlockCanvas } from '@wordpress/block-editor'; - -export const editorStyles = [ +const styles = [ { css: ` body { @@ -36,14 +65,6 @@ export const editorStyles = [ `, }, ]; - -export default function App() { - return ( - - - - ); -} ``` ## mediaUpload @@ -91,7 +112,7 @@ Providing a `mediaUpload` function also enables drag and dropping files into the The inserter media categories setting is an array of media categories to display in the inserter. Each category is an object with `name` and `labels` values, a `fetch` function and a few extra keys. Example: ```jsx -{ +const inserterMediaCategories = { name: 'openverse', labels: { name: 'Openverse', diff --git a/platform-docs/docs/create-block/attributes.md b/platform-docs/docs/create-block/attributes.md index a2da8ee5c3694c..6e53c04ad7f8ea 100644 --- a/platform-docs/docs/create-block/attributes.md +++ b/platform-docs/docs/create-block/attributes.md @@ -46,29 +46,29 @@ registerBlockType( 'gutenpride/gutenpride-block', { } ); ``` -## TextControl Component +## PlainText Component -For our example block, the component we are going to use is the **TextControl** component, which is similar to an HTML text input field. You can see [the documentation for the `TextControl` component](https://developer.wordpress.org/block-editor/reference-guides/components/text-control/). You can browse an [interactive set of components in this Storybook](https://wordpress.github.io/gutenberg/). +For our example block, the component we are going to use is the **PlainText** component, which allows the user to type some unformatted text. The **PlainText** component is imported from the `@wordpress/block-editor` package. -The component is added similar to an HTML tag, setting a label, the `value` is set to the `attributes.message` and the `onChange` function uses the `setAttributes` to update the message attribute value. +The component is added similar to an HTML tag, the `value` is set to the `attributes.message` and the `onChange` function uses the `setAttributes` to update the message attribute value. The save function will simply write the `attributes.message` as a `div` tag since that is how we defined it to be parsed. Update the `edit.js` and `save.js` files to the following, replacing the existing functions. **edit.js** file: ```js -import { useBlockProps } from '@wordpress/block-editor'; -import { TextControl } from '@wordpress/components'; +import { useBlockProps, PlainText } from '@wordpress/block-editor'; function Edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); return ( - - setAttributes( { message: val } ) } - /> - + setAttributes( { message: val } ) } + __experimentalVersion={ 2 } + /> ); } ``` diff --git a/platform-docs/docs/create-block/block-supports.md b/platform-docs/docs/create-block/block-supports.md index 4a245f210d6547..af139d9dfdd807 100644 --- a/platform-docs/docs/create-block/block-supports.md +++ b/platform-docs/docs/create-block/block-supports.md @@ -1,3 +1,33 @@ --- -sidebar_position: 4 +sidebar_position: 3 --- + +# Block Supports + +A lot of blocks, including core blocks, offer similar customization options. Whether that is to change the background color, text color, or to add padding, margin customization options. + +To avoid duplicating the same logic over and over in your blocks and to align the behavior of your block with core blocks, Gutenberg provides a list of reusable block supports. + +Let's augment our Gutenberg pride block with some of these supports. To do so, we just update the `registerBlockType` call with an additional `supports` key like so: + +```jsx +registerBlockType( 'gutenpride/gutenpride-block', { + // ... + supports: { + color: { + text: true, + background: true, + }, + }, +} ); +``` + +If your block editor allows text and background colors, the block inspector will now show a panel that allows users to customize these colors for the selected block. + +In addition to colors, the block editor provides by default a number of built-in block supports that any block type can use to quickly add customization options. These block supports include: + + - colors + - typography + - borders + - dimensions and spacing + - and more... diff --git a/platform-docs/docs/create-block/nested-blocks.md b/platform-docs/docs/create-block/nested-blocks.md index 78352ba3ebe43f..cfce09181177a5 100644 --- a/platform-docs/docs/create-block/nested-blocks.md +++ b/platform-docs/docs/create-block/nested-blocks.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 4 --- # Nested blocks \ No newline at end of file diff --git a/platform-docs/docs/create-block/transforms.md b/platform-docs/docs/create-block/transforms.md index ab631d44e14208..2f9fa40aceb2b4 100644 --- a/platform-docs/docs/create-block/transforms.md +++ b/platform-docs/docs/create-block/transforms.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 4 --- # Block Transforms \ No newline at end of file diff --git a/platform-docs/docs/create-block/using-styles.md b/platform-docs/docs/create-block/using-styles.md deleted file mode 100644 index c8d1bb91d5961d..00000000000000 --- a/platform-docs/docs/create-block/using-styles.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Using styles and stylesheets \ No newline at end of file diff --git a/platform-docs/docs/create-block/writing-flow.md b/platform-docs/docs/create-block/writing-flow.md index 187d39ba35c5e3..452e060f01b1a8 100644 --- a/platform-docs/docs/create-block/writing-flow.md +++ b/platform-docs/docs/create-block/writing-flow.md @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 6 --- # Writing Flow and Pasting \ No newline at end of file diff --git a/test/e2e/specs/editor/plugins/custom-taxonomies.spec.js b/test/e2e/specs/editor/plugins/custom-taxonomies.spec.js new file mode 100644 index 00000000000000..9ac8df327a3a11 --- /dev/null +++ b/test/e2e/specs/editor/plugins/custom-taxonomies.spec.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Custom Taxonomies labels are used', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( 'gutenberg-test-custom-taxonomies' ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-custom-taxonomies' + ); + } ); + + test( 'Ensures the custom taxonomy labels are respected', async ( { + editor, + page, + } ) => { + await editor.openDocumentSettingsSidebar(); + const editorSettings = page.getByRole( 'region', { + name: 'Editor settings', + } ); + const modelPanel = editorSettings.getByRole( 'button', { + name: 'Model', + } ); + + // Open the panel if needed. + if ( + ( await modelPanel.getAttribute( 'aria-expanded' ) ) === 'false' + ) { + await modelPanel.click(); + } + + // Check the add new button. + await editorSettings + .getByRole( 'combobox', { name: 'Add New Model' } ) + .fill( 'Model 1' ); + await page.keyboard.press( 'Enter' ); + + await expect( + editorSettings.getByRole( 'button', { name: 'Remove Mode' } ) + ).toBeVisible(); + } ); +} ); diff --git a/test/e2e/specs/editor/various/block-renaming.spec.js b/test/e2e/specs/editor/various/block-renaming.spec.js index f8d9548fbe8667..19a2818fd3a411 100644 --- a/test/e2e/specs/editor/various/block-renaming.spec.js +++ b/test/e2e/specs/editor/various/block-renaming.spec.js @@ -4,8 +4,30 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); test.describe( 'Block Renaming', () => { - test.beforeEach( async ( { admin } ) => { + test.beforeEach( async ( { admin, page } ) => { await admin.createNewPost(); + + // Registering block must be after creation of Post. + await page.evaluate( () => { + const registerBlockType = window.wp.blocks.registerBlockType; + + registerBlockType( + 'my-plugin/block-that-does-not-support-renaming', + { + title: 'No Rename Support Block', + icon: 'smiley', + supports: { + renaming: false, + }, + edit() { + return null; + }, + save() { + return null; + }, + } + ); + } ); } ); test.describe( 'Dialog renaming', () => { @@ -23,18 +45,12 @@ test.describe( 'Block Renaming', () => { name: 'Block navigation structure', } ); - // Create a two blocks on the page. await editor.insertBlock( { - name: 'core/paragraph', + name: 'core/group', attributes: { content: 'First Paragraph' }, } ); - await editor.insertBlock( { - name: 'core/paragraph', - attributes: { content: 'Second Paragraph' }, - } ); - // Multiselect via keyboard. - await pageUtils.pressKeys( 'primary+a' ); + // Select via keyboard. await pageUtils.pressKeys( 'primary+a' ); // Convert to a Group block which supports renaming. @@ -144,46 +160,99 @@ test.describe( 'Block Renaming', () => { ] ); } ); - test( 'does not allow renaming of blocks that do not support renaming', async ( { - // use `core/template-part` as the block + test( 'does not allow renaming of blocks that do not support the feature', async ( { editor, page, + pageUtils, } ) => { + await pageUtils.pressKeys( 'access+o' ); + + const listView = page.getByRole( 'treegrid', { + name: 'Block navigation structure', + } ); + await editor.insertBlock( { - name: 'core/navigation', + name: 'my-plugin/block-that-does-not-support-renaming', } ); - // Opens the block options menu and check there is not a `Rename` option - await editor.clickBlockToolbarButton( 'Options' ); - // + // Select via keyboard. + await pageUtils.pressKeys( 'primary+a' ); - const renameMenuItem = page.getByRole( 'menuitem', { - name: 'Rename', + const blockOptionsTrigger = listView.getByRole( 'button', { + name: 'Options for No Rename Support Block', } ); - // TODO: assert that the locator didn't find a DOM node at all. + await blockOptionsTrigger.click(); + + const renameMenuItem = page + .getByRole( 'menu', { + name: 'Options for No Rename Support Block', + } ) + .getByRole( 'menuitem', { + name: 'Rename', + } ); + + // Expect the Rename menu item not to exist at all. await expect( renameMenuItem ).toBeHidden(); } ); - } ); - test.describe( 'Block inspector renaming', () => { - test( 'allows renaming of blocks that support the feature via "Advanced" section of block inspector tools', async ( { + test( 'displays Rename option in related menu when block is not selected', async ( { editor, page, pageUtils, } ) => { - // Create a two blocks on the page. + await pageUtils.pressKeys( 'access+o' ); + + const listView = page.getByRole( 'treegrid', { + name: 'Block navigation structure', + } ); + await editor.insertBlock( { - name: 'core/paragraph', - attributes: { content: 'First Paragraph' }, + name: 'core/heading', } ); + await editor.insertBlock( { name: 'core/paragraph', - attributes: { content: 'Second Paragraph' }, } ); - // Multiselect via keyboard. - await pageUtils.pressKeys( 'primary+a' ); + // Select the Paragraph block. + await listView + .getByRole( 'link', { + name: 'Paragraph', + } ) + .click(); + + // Trigger options menu for the Heading (not the selected block). + const blockOptionsTrigger = listView.getByRole( 'button', { + name: 'Options for Heading', + } ); + + await blockOptionsTrigger.click(); + + const renameMenuItem = page + .getByRole( 'menu', { + name: 'Options for Heading', + } ) + .getByRole( 'menuitem', { + name: 'Rename', + } ); + + // Expect the Rename menu item not to exist at all. + await expect( renameMenuItem ).toBeVisible(); + } ); + } ); + + test.describe( 'Block inspector renaming', () => { + test( 'allows renaming of blocks that support the feature via "Advanced" section of block inspector tools', async ( { + editor, + page, + pageUtils, + } ) => { + await editor.insertBlock( { + name: 'core/group', + } ); + + // Select via keyboard. await pageUtils.pressKeys( 'primary+a' ); // Convert to a Group block which supports renaming. @@ -239,23 +308,19 @@ test.describe( 'Block Renaming', () => { ] ); } ); - test( 'does not allow renaming of blocks that do not support renaming', async ( { + test( 'does not allow renaming of blocks that do not support the feature', async ( { editor, page, + pageUtils, } ) => { await editor.insertBlock( { - name: 'core/navigation', + name: 'my-plugin/block-that-does-not-support-renaming', } ); - await editor.openDocumentSettingsSidebar(); - - const settingsTab = page - .getByRole( 'region', { - name: 'Editor settings', - } ) - .getByRole( 'tab', { name: 'Settings' } ); + // Multiselect via keyboard. + await pageUtils.pressKeys( 'primary+a' ); - await settingsTab.click(); + await editor.openDocumentSettingsSidebar(); const advancedPanelToggle = page .getByRole( 'region', { @@ -268,11 +333,12 @@ test.describe( 'Block Renaming', () => { await advancedPanelToggle.click(); - const nameInput = page.getByRole( 'textbox', { - name: 'Block name', - } ); - - await expect( nameInput ).toBeHidden(); + // Expect the Rename control not to exist at all. + await expect( + page.getByRole( 'textbox', { + name: 'Block name', + } ) + ).toBeHidden(); } ); } ); } );
+ { __( 'Enter a custom name for this block.' ) } +
- { __( 'Enter a custom name for this block.' ) } -