diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js index 66f2a08f115a4a..3c66c5beb08d38 100644 --- a/packages/block-editor/src/components/editor-styles/index.js +++ b/packages/block-editor/src/components/editor-styles/index.js @@ -43,13 +43,13 @@ function useDarkThemeBodyClassName( styles ) { body.appendChild( tempCanvas ); backgroundColor = defaultView - .getComputedStyle( tempCanvas, null ) + ?.getComputedStyle( tempCanvas, null ) .getPropertyValue( 'background-color' ); body.removeChild( tempCanvas ); } else { backgroundColor = defaultView - .getComputedStyle( canvas, null ) + ?.getComputedStyle( canvas, null ) .getPropertyValue( 'background-color' ); } const colordBackgroundColor = colord( backgroundColor ); diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index d5ad1375031698..13f77203c410fe 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -331,7 +331,8 @@ export function useSettingsForBlockElement( const sides = Array.isArray( supports?.spacing?.[ key ] ) ? supports?.spacing?.[ key ] : supports?.spacing?.[ key ]?.sides; - if ( sides?.length ) { + // Check if spacing type is supported before adding sides. + if ( sides?.length && updatedSettings.spacing?.[ key ] ) { updatedSettings.spacing = { ...updatedSettings.spacing, [ key ]: { diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index aa5bfe02992928..a1a369d3f94084 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -489,7 +489,11 @@ export default function useListViewDropZone( { dropZoneElement } ) { const ref = useDropZone( { dropZoneElement, - onDrop: onBlockDrop, + onDrop( event ) { + if ( target ) { + onBlockDrop( event ); + } + }, onDragLeave() { throttled.cancel(); setTarget( null ); diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js index 75b077ab321d43..a70b9f8f778815 100644 --- a/packages/block-editor/src/components/rich-text/format-edit.js +++ b/packages/block-editor/src/components/rich-text/format-edit.js @@ -2,43 +2,67 @@ * WordPress dependencies */ import { getActiveFormat, getActiveObject } from '@wordpress/rich-text'; +import { useContext, useMemo } from '@wordpress/element'; -export default function FormatEdit( { - formatTypes, - onChange, - onFocus, - value, - forwardedRef, -} ) { - return formatTypes.map( ( settings ) => { - const { name, edit: Edit } = settings; - - if ( ! Edit ) { - return null; - } - - const activeFormat = getActiveFormat( value, name ); - const isActive = activeFormat !== undefined; - const activeObject = getActiveObject( value ); - const isObjectActive = - activeObject !== undefined && activeObject.type === name; - - return ( - - ); - } ); +/** + * Internal dependencies + */ +import BlockContext from '../block-context'; + +const DEFAULT_BLOCK_CONTEXT = {}; + +export const usesContextKey = Symbol( 'usesContext' ); + +function Edit( { onChange, onFocus, value, forwardedRef, settings } ) { + const { + name, + edit: EditFunction, + [ usesContextKey ]: usesContext, + } = settings; + + const blockContext = useContext( BlockContext ); + + // Assign context values using the block type's declared context needs. + const context = useMemo( () => { + return usesContext + ? Object.fromEntries( + Object.entries( blockContext ).filter( ( [ key ] ) => + usesContext.includes( key ) + ) + ) + : DEFAULT_BLOCK_CONTEXT; + }, [ usesContext, blockContext ] ); + + if ( ! EditFunction ) { + return null; + } + + const activeFormat = getActiveFormat( value, name ); + const isActive = activeFormat !== undefined; + const activeObject = getActiveObject( value ); + const isObjectActive = + activeObject !== undefined && activeObject.type === name; + + return ( + + ); +} + +export default function FormatEdit( { formatTypes, ...props } ) { + return formatTypes.map( ( settings ) => ( + + ) ); } diff --git a/packages/block-editor/src/components/spacing-sizes-control/test/utils.js b/packages/block-editor/src/components/spacing-sizes-control/test/utils.js index d6b2fa2b4850bd..a631cbfcff3ef1 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/test/utils.js +++ b/packages/block-editor/src/components/spacing-sizes-control/test/utils.js @@ -49,7 +49,15 @@ describe( 'getCustomValueFromPreset', () => { } ); describe( 'getPresetValueFromCustomValue', () => { - const spacingSizes = [ { name: 'Small', slug: 20, size: '8px' } ]; + const spacingSizes = [ + { name: 'Default', slug: 'default', size: undefined }, + { name: 'Small', slug: 20, size: '8px' }, + ]; + it( 'should return undefined even if an undefined value exist in spacing sizes as occurs if spacingSizes has > 7 entries', () => { + expect( getPresetValueFromCustomValue( undefined, spacingSizes ) ).toBe( + undefined + ); + } ); it( 'should return original value if a string in spacing presets var format', () => { expect( getPresetValueFromCustomValue( diff --git a/packages/block-editor/src/components/spacing-sizes-control/utils.js b/packages/block-editor/src/components/spacing-sizes-control/utils.js index 340abb0322e9a2..7ca0e1b6f660fd 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/utils.js +++ b/packages/block-editor/src/components/spacing-sizes-control/utils.js @@ -101,8 +101,8 @@ export function getCustomValueFromPreset( value, spacingSizes ) { * @return {string} The preset value if it can be found. */ export function getPresetValueFromCustomValue( value, spacingSizes ) { - // Return value as-is if it is already a preset; - if ( isValueSpacingPreset( value ) || value === '0' ) { + // Return value as-is if it is undefined or is already a preset, or '0'; + if ( ! value || isValueSpacingPreset( value ) || value === '0' ) { return value; } diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index f2946ae9e89eea..b8a11eb8569503 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -22,6 +22,7 @@ import { default as ReusableBlocksRenameHint, useReusableBlocksRenameHint, } from './components/inserter/reusable-block-rename-hint'; +import { usesContextKey } from './components/rich-text/format-edit'; /** * Private @wordpress/block-editor APIs. @@ -47,4 +48,5 @@ lock( privateApis, { ResolutionTool, ReusableBlocksRenameHint, useReusableBlocksRenameHint, + usesContextKey, } ); diff --git a/packages/block-library/src/footnotes/edit.js b/packages/block-library/src/footnotes/edit.js index fdfe7a94039af9..b8b92170fe217f 100644 --- a/packages/block-library/src/footnotes/edit.js +++ b/packages/block-library/src/footnotes/edit.js @@ -17,6 +17,18 @@ export default function FootnotesEdit( { context: { postType, postId } } ) { const footnotes = meta?.footnotes ? JSON.parse( meta.footnotes ) : []; const blockProps = useBlockProps(); + if ( postType !== 'post' && postType !== 'page' ) { + return ( +
+ } + label={ __( 'Footnotes' ) } + // To do: add instructions. We can't add new string in RC. + /> +
+ ); + } + if ( ! footnotes.length ) { return (
diff --git a/packages/block-library/src/footnotes/format.js b/packages/block-library/src/footnotes/format.js index eb700787d02eeb..2086005a509931 100644 --- a/packages/block-library/src/footnotes/format.js +++ b/packages/block-library/src/footnotes/format.js @@ -12,14 +12,18 @@ import { insertObject } from '@wordpress/rich-text'; import { RichTextToolbarButton, store as blockEditorStore, + privateApis, } from '@wordpress/block-editor'; import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies */ import { name } from './block.json'; +import { unlock } from '../lock-unlock'; + +const { usesContextKey } = unlock( privateApis ); export const formatName = 'core/footnote'; export const format = { @@ -30,7 +34,13 @@ export const format = { 'data-fn': 'data-fn', }, contentEditable: false, - edit: function Edit( { value, onChange, isObjectActive } ) { + [ usesContextKey ]: [ 'postType' ], + edit: function Edit( { + value, + onChange, + isObjectActive, + context: { postType }, + } ) { const registry = useRegistry(); const { getSelectedBlockClientId, @@ -38,9 +48,20 @@ export const format = { getBlockName, getBlocks, } = useSelect( blockEditorStore ); + const footnotesBlockType = useSelect( ( select ) => + select( blocksStore ).getBlockType( name ) + ); const { selectionChange, insertBlock } = useDispatch( blockEditorStore ); + if ( ! footnotesBlockType ) { + return null; + } + + if ( postType !== 'post' && postType !== 'page' ) { + return null; + } + function onClick() { registry.batch( () => { let id; diff --git a/packages/block-library/src/footnotes/index.js b/packages/block-library/src/footnotes/index.js index c0f3d60ada5432..c5e851af7e033f 100644 --- a/packages/block-library/src/footnotes/index.js +++ b/packages/block-library/src/footnotes/index.js @@ -21,7 +21,6 @@ export const settings = { edit, }; -// Would be good to remove the format and HoR if the block is unregistered. registerFormatType( formatName, format ); export const init = () => { diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php index 1066aa01419159..86a1feefd6dbc9 100644 --- a/packages/block-library/src/template-part/index.php +++ b/packages/block-library/src/template-part/index.php @@ -250,7 +250,7 @@ function build_template_part_block_instance_variations() { 'area' => $template_part->area, ), 'scope' => array( 'inserter' ), - 'icon' => $icon_by_area[ $template_part->area ], + 'icon' => isset( $icon_by_area[ $template_part->area ] ) ? $icon_by_area[ $template_part->area ] : null, 'example' => array( 'attributes' => array( 'slug' => $template_part->slug, diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js index b4a828f34303db..aa77925763007d 100644 --- a/packages/commands/src/components/command-menu.js +++ b/packages/commands/src/components/command-menu.js @@ -201,6 +201,20 @@ export function CommandMenu() { if ( ! isOpen ) { return false; } + + const onKeyDown = ( event ) => { + if ( + // Ignore keydowns from IMEs + event.nativeEvent.isComposing || + // Workaround for Mac Safari where the final Enter/Backspace of an IME composition + // is `isComposing=false`, even though it's technically still part of the composition. + // These can only be detected by keyCode. + event.keyCode === 229 + ) { + event.preventDefault(); + } + }; + const isLoading = Object.values( loaders ).some( Boolean ); return ( @@ -211,7 +225,10 @@ export function CommandMenu() { __experimentalHideHeader >
- +
{ const { isEditingTemplate, getEditedPostTemplate } = select( editPostStore ); @@ -46,24 +46,26 @@ function DocumentTitle() { } return ( -
- - - - +
+ -
); } -export default DocumentTitle; +export default DocumentActions; diff --git a/packages/edit-post/src/components/header/document-title/style.scss b/packages/edit-post/src/components/header/document-actions/style.scss similarity index 64% rename from packages/edit-post/src/components/header/document-title/style.scss rename to packages/edit-post/src/components/header/document-actions/style.scss index e39ecf607e4306..7eb77f9c0bd88c 100644 --- a/packages/edit-post/src/components/header/document-title/style.scss +++ b/packages/edit-post/src/components/header/document-actions/style.scss @@ -1,4 +1,4 @@ -.edit-post-document-title { +.edit-post-document-actions { display: flex; align-items: center; gap: $grid-unit; @@ -13,13 +13,21 @@ border-radius: 4px; width: min(100%, 450px); - &:hover { - color: currentColor; - background: $gray-200; + .components-button { + &:hover { + color: var(--wp-block-synced-color); + background: $gray-200; + } } } -.edit-post-document-title__title.components-button { +.edit-post-document-actions__command { + flex-grow: 1; + color: var(--wp-block-synced-color); + overflow: hidden; +} + +.edit-post-document-actions__title { flex-grow: 1; color: var(--wp-block-synced-color); overflow: hidden; @@ -28,34 +36,29 @@ color: var(--wp-block-synced-color); } + .block-editor-block-icon { + flex-shrink: 0; + } + h1 { - color: var(--wp-block-synced-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + color: var(--wp-block-synced-color); } } -.edit-post-document-title__shortcut { - flex-shrink: 0; - color: $gray-700; - padding: 0 $grid-unit-15; - - &:hover { - color: $gray-700; - } +.edit-post-document-actions__shortcut { + color: $gray-800; } -.edit-post-document-title__left { +.edit-post-document-actions__back.components-button.has-icon.has-text { min-width: $button-size; flex-shrink: 0; + color: $gray-700; + gap: 0; - .components-button.has-icon.has-text { - color: $gray-700; - gap: 0; - - &:hover { - color: currentColor; - } + &:hover { + color: currentColor; } } diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 3f42d4736f57bb..6705d953c67594 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -18,7 +18,7 @@ import { default as DevicePreview } from '../device-preview'; import ViewLink from '../view-link'; import MainDashboardButton from './main-dashboard-button'; import { store as editPostStore } from '../../store'; -import DocumentTitle from './document-title'; +import DocumentActions from './document-actions'; const slideY = { hidden: { y: '-50px' }, @@ -65,8 +65,8 @@ function Header( { setEntitiesSavedStatesCallback } ) { className="edit-post-header__toolbar" > -
- +
+
{ event.stopPropagation(); onBack(); diff --git a/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss b/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss index d26bbdaf28ff64..dd442264e63980 100644 --- a/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss +++ b/packages/edit-site/src/components/header-edit-mode/document-actions/style.scss @@ -1,7 +1,9 @@ .edit-site-document-actions { - display: grid; - grid-template-columns: 1fr 2fr 1fr; + display: flex; + align-items: center; + gap: $grid-unit; height: $button-size; + justify-content: space-between; // Flex items will, by default, refuse to shrink below a minimum // intrinsic width. In order to shrink this flexbox item, and // subsequently truncate child text, we set an explicit min-width. @@ -10,11 +12,12 @@ background: $gray-100; border-radius: 4px; width: min(100%, 450px); - overflow: hidden; - &:hover { - color: currentColor; - background: $gray-200; + .components-button { + &:hover { + color: var(--wp-block-synced-color); + background: $gray-200; + } } @include break-medium() { @@ -27,21 +30,23 @@ } .edit-site-document-actions__command { - grid-column: 1 / -1; - display: grid; - grid-template-columns: 1fr 2fr 1fr; - grid-row: 1; + flex-grow: 1; + color: var(--wp-block-synced-color); + overflow: hidden; } - .edit-site-document-actions__title { flex-grow: 1; color: var(--wp-block-synced-color); overflow: hidden; - grid-column: 2 / 3; + + &:hover { + color: var(--wp-block-synced-color); + } .block-editor-block-icon { min-width: $grid-unit-30; + flex-shrink: 0; } h1 { @@ -70,26 +75,21 @@ } } -.edit-site-document-actions__shortcut, -.edit-site-document-actions__back { - color: $gray-800; - - .edit-site-document-actions:hover & { - color: $gray-900; - } -} - .edit-site-document-actions__shortcut { - text-align: right; + color: $gray-800; } -.edit-site-document-actions__back { +.edit-site-document-actions__back.components-button.has-icon.has-text { min-width: $button-size; flex-shrink: 0; - grid-column: 1 / 2; - grid-row: 1; + color: $gray-700; + gap: 0; z-index: 1; + &:hover { + color: currentColor; + } + .edit-site-document-actions.is-animated & { animation: edit-site-document-actions__slide-in-left 0.3s; @include reduce-motion("animation"); diff --git a/packages/edit-site/src/components/page-patterns/grid.js b/packages/edit-site/src/components/page-patterns/grid.js index 47bcdc8a1f768e..efbf8a6989a32f 100644 --- a/packages/edit-site/src/components/page-patterns/grid.js +++ b/packages/edit-site/src/components/page-patterns/grid.js @@ -6,7 +6,7 @@ import { __experimentalText as Text, Button, } from '@wordpress/components'; -import { useRef, useState, useMemo } from '@wordpress/element'; +import { useRef, useMemo } from '@wordpress/element'; import { __, _x, _n, sprintf } from '@wordpress/i18n'; import { useAsyncList } from '@wordpress/compose'; @@ -82,8 +82,13 @@ function Pagination( { currentPage, numPages, changePage, totalItems } ) { ); } -export default function Grid( { categoryId, items, ...props } ) { - const [ currentPage, setCurrentPage ] = useState( 1 ); +export default function Grid( { + categoryId, + items, + currentPage, + setCurrentPage, + ...props +} ) { const gridRef = useRef(); const totalItems = items.length; const pageIndex = currentPage - 1; diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js index 9ebfc5dfe5c98f..725cc0f01850da 100644 --- a/packages/edit-site/src/components/page-patterns/patterns-list.js +++ b/packages/edit-site/src/components/page-patterns/patterns-list.js @@ -48,6 +48,7 @@ const SYNC_DESCRIPTIONS = { }; export default function PatternsList( { categoryId, type } ) { + const [ currentPage, setCurrentPage ] = useState( 1 ); const location = useLocation(); const history = useHistory(); const isMobileViewport = useViewportMatch( 'medium', '<' ); @@ -73,6 +74,16 @@ export default function PatternsList( { categoryId, type } ) { } ); + const updateSearchFilter = ( value ) => { + setCurrentPage( 1 ); + setFilterValue( value ); + }; + + const updateSyncFilter = ( value ) => { + setCurrentPage( 1 ); + setSyncFilter( value ); + }; + const id = useId(); const titleId = `${ id }-title`; const descriptionId = `${ id }-description`; @@ -109,7 +120,7 @@ export default function PatternsList( { categoryId, type } ) { setFilterValue( value ) } + onChange={ ( value ) => updateSearchFilter( value ) } placeholder={ __( 'Search patterns' ) } label={ __( 'Search patterns' ) } value={ filterValue } @@ -123,7 +134,7 @@ export default function PatternsList( { categoryId, type } ) { label={ __( 'Filter by sync status' ) } value={ syncFilter } isBlock - onChange={ ( value ) => setSyncFilter( value ) } + onChange={ ( value ) => updateSyncFilter( value ) } __nextHasNoMarginBottom > { Object.entries( SYNC_FILTERS ).map( @@ -157,6 +168,8 @@ export default function PatternsList( { categoryId, type } ) { items={ patterns } aria-labelledby={ titleId } aria-describedby={ descriptionId } + currentPage={ currentPage } + setCurrentPage={ setCurrentPage } /> ) } { ! isResolving && ! hasPatterns && } diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index 37b4fcce6cfa7e..d39d7372101195 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -4,6 +4,7 @@ import { parse } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; /** @@ -39,14 +40,12 @@ const templatePartToPattern = ( templatePart ) => ( { templatePart, } ); -const templatePartHasCategory = ( item, category ) => - item.templatePart.area === category; - const selectTemplatePartsAsPatterns = ( select, { categoryId, search = '' } = {} ) => { const { getEntityRecords, getIsResolving } = select( coreStore ); + const { __experimentalGetDefaultTemplatePartAreas } = select( editorStore ); const query = { per_page: -1 }; const rawTemplateParts = getEntityRecords( 'postType', TEMPLATE_PARTS, query ) ?? @@ -55,6 +54,23 @@ const selectTemplatePartsAsPatterns = ( templatePartToPattern( templatePart ) ); + // In the case where a custom template part area has been removed we need + // the current list of areas to cross check against so orphaned template + // parts can be treated as uncategorized. + const knownAreas = __experimentalGetDefaultTemplatePartAreas() || []; + const templatePartAreas = knownAreas.map( ( area ) => area.area ); + + const templatePartHasCategory = ( item, category ) => { + if ( category !== 'uncategorized' ) { + return item.templatePart.area === category; + } + + return ( + item.templatePart.area === category || + ! templatePartAreas.includes( item.templatePart.area ) + ); + }; + const isResolving = getIsResolving( 'getEntityRecords', [ 'postType', 'wp_template_part', diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js index 6e5096bee3fbe8..894cd5f8048183 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/category-item.js @@ -12,19 +12,11 @@ export default function CategoryItem( { label, type, } ) { - const linkInfo = useLink( - { - path: '/patterns', - categoryType: type, - categoryId: id, - }, - { - // Keep a record of where we came from in state so we can - // use the browser's back button to go back to Patterns. - // See the implementation of the back button in patterns-list. - backPath: '/patterns', - } - ); + const linkInfo = useLink( { + path: '/patterns', + categoryType: type, + categoryId: id, + } ); if ( ! count ) { return; diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-canvas-mode-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-canvas-mode-with-url.js index 735c80cdb531c6..9f0c8dd9a0e11f 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-sync-canvas-mode-with-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-sync-canvas-mode-with-url.js @@ -58,10 +58,7 @@ export default function useSyncCanvasModeWithURL() { useEffect( () => { currentCanvasInUrl.current = canvasInUrl; - if ( - canvasInUrl === undefined && - currentCanvasMode.current !== 'view' - ) { + if ( canvasInUrl !== 'edit' && currentCanvasMode.current !== 'view' ) { setCanvasMode( 'view' ); } else if ( canvasInUrl === 'edit' && diff --git a/packages/rich-text/src/component/use-selection-change-compat.js b/packages/rich-text/src/component/use-selection-change-compat.js index 7a684f584263e4..d067d5ec70ff7f 100644 --- a/packages/rich-text/src/component/use-selection-change-compat.js +++ b/packages/rich-text/src/component/use-selection-change-compat.js @@ -21,7 +21,7 @@ export function useSelectionChangeCompat() { return useRefEffect( ( element ) => { const { ownerDocument } = element; const { defaultView } = ownerDocument; - const selection = defaultView.getSelection(); + const selection = defaultView?.getSelection(); let range;