diff --git a/backport-changelog/6.8/7069.md b/backport-changelog/6.8/7069.md new file mode 100644 index 00000000000000..ea3c717ec3c93a --- /dev/null +++ b/backport-changelog/6.8/7069.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7069 + +* https://github.com/WordPress/gutenberg/pull/63401 diff --git a/backport-changelog/6.8/7698.md b/backport-changelog/6.8/7698.md new file mode 100644 index 00000000000000..3ded160e7ec449 --- /dev/null +++ b/backport-changelog/6.8/7698.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7698 + +* https://github.com/WordPress/gutenberg/pull/66662 diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index a4719b7bdd4099..21086b94f15c1a 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -20,16 +20,16 @@ function gutenberg_register_typography_support( $block_type ) { return; } - $has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false; + $has_font_family_support = $typography_supports['fontFamily'] ?? false; $has_font_size_support = $typography_supports['fontSize'] ?? false; - $has_font_style_support = $typography_supports['__experimentalFontStyle'] ?? false; - $has_font_weight_support = $typography_supports['__experimentalFontWeight'] ?? false; - $has_letter_spacing_support = $typography_supports['__experimentalLetterSpacing'] ?? false; + $has_font_style_support = $typography_supports['fontStyle'] ?? false; + $has_font_weight_support = $typography_supports['fontWeight'] ?? false; + $has_letter_spacing_support = $typography_supports['letterSpacing'] ?? false; $has_line_height_support = $typography_supports['lineHeight'] ?? false; $has_text_align_support = $typography_supports['textAlign'] ?? false; $has_text_columns_support = $typography_supports['textColumns'] ?? false; - $has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false; - $has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false; + $has_text_decoration_support = $typography_supports['textDecoration'] ?? false; + $has_text_transform_support = $typography_supports['textTransform'] ?? false; $has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false; $has_typography_support = $has_font_family_support @@ -91,16 +91,16 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { return array(); } - $has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false; + $has_font_family_support = $typography_supports['fontFamily'] ?? false; $has_font_size_support = $typography_supports['fontSize'] ?? false; - $has_font_style_support = $typography_supports['__experimentalFontStyle'] ?? false; - $has_font_weight_support = $typography_supports['__experimentalFontWeight'] ?? false; - $has_letter_spacing_support = $typography_supports['__experimentalLetterSpacing'] ?? false; + $has_font_style_support = $typography_supports['fontStyle'] ?? false; + $has_font_weight_support = $typography_supports['fontWeight'] ?? false; + $has_letter_spacing_support = $typography_supports['letterSpacing'] ?? false; $has_line_height_support = $typography_supports['lineHeight'] ?? false; $has_text_align_support = $typography_supports['textAlign'] ?? false; $has_text_columns_support = $typography_supports['textColumns'] ?? false; - $has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false; - $has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false; + $has_text_decoration_support = $typography_supports['textDecoration'] ?? false; + $has_text_transform_support = $typography_supports['textTransform'] ?? false; $has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false; // Whether to skip individual block support features. diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 2231cb0f11538f..cd02b5a45c22f7 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -920,18 +920,14 @@ public static function resolve_theme_file_uris( $theme_json ) { return $theme_json; } - $resolved_theme_json_data = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - ); + $resolved_theme_json_data = $theme_json->get_raw_data(); foreach ( $resolved_urls as $resolved_url ) { $path = explode( '.', $resolved_url['target'] ); _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); } - $theme_json->merge( new WP_Theme_JSON_Gutenberg( $resolved_theme_json_data ) ); - - return $theme_json; + return new WP_Theme_JSON_Gutenberg( $resolved_theme_json_data ); } /** diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php new file mode 100644 index 00000000000000..3124dd2a12a615 --- /dev/null +++ b/lib/compat/wordpress-6.8/blocks.php @@ -0,0 +1,47 @@ + 'fontFamily', + '__experimentalFontStyle' => 'fontStyle', + '__experimentalFontWeight' => 'fontWeight', + '__experimentalLetterSpacing' => 'letterSpacing', + '__experimentalTextDecoration' => 'textDecoration', + '__experimentalTextTransform' => 'textTransform', + ); + + $current_typography_supports = $args['supports']['typography']; + $stable_typography_supports = array(); + + foreach ( $current_typography_supports as $key => $value ) { + if ( array_key_exists( $key, $experimental_typography_supports_to_stable ) ) { + $stable_typography_supports[ $experimental_typography_supports_to_stable[ $key ] ] = $value; + } else { + $stable_typography_supports[ $key ] = $value; + } + } + + $args['supports']['typography'] = $stable_typography_supports; + + return $args; +} + +add_filter( 'register_block_type_args', 'gutenberg_stabilize_experimental_block_supports', PHP_INT_MAX, 1 ); diff --git a/lib/load.php b/lib/load.php index 0540d4cd9efac7..6236f0eb04b3c6 100644 --- a/lib/load.php +++ b/lib/load.php @@ -118,6 +118,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.8 compat. require __DIR__ . '/compat/wordpress-6.8/preload.php'; +require __DIR__ . '/compat/wordpress-6.8/blocks.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index e04dfd8e9a480a..dd9d8f254ff76b 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -10,7 +10,8 @@ margin-bottom: 1.5em; } - .components-base-control { + .components-base-control, + .components-radio-control { &:where(:not(:last-child)) { margin-bottom: $grid-unit-20; } diff --git a/packages/block-editor/src/hooks/font-family.js b/packages/block-editor/src/hooks/font-family.js index ba9a66a8bcf04f..e5d8e02ab8ec02 100644 --- a/packages/block-editor/src/hooks/font-family.js +++ b/packages/block-editor/src/hooks/font-family.js @@ -13,7 +13,7 @@ import { shouldSkipSerialization } from './utils'; import { TYPOGRAPHY_SUPPORT_KEY } from './typography'; import { unlock } from '../lock-unlock'; -export const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; +export const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; const { kebabCase } = unlock( componentsPrivateApis ); /** diff --git a/packages/block-editor/src/hooks/supports.js b/packages/block-editor/src/hooks/supports.js index 75f2bdf2dc219e..c0b6bb2cc8b271 100644 --- a/packages/block-editor/src/hooks/supports.js +++ b/packages/block-editor/src/hooks/supports.js @@ -9,17 +9,17 @@ const ALIGN_WIDE_SUPPORT_KEY = 'alignWide'; const BORDER_SUPPORT_KEY = '__experimentalBorder'; const COLOR_SUPPORT_KEY = 'color'; const CUSTOM_CLASS_NAME_SUPPORT_KEY = 'customClassName'; -const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; +const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; const FONT_SIZE_SUPPORT_KEY = 'typography.fontSize'; const LINE_HEIGHT_SUPPORT_KEY = 'typography.lineHeight'; /** * Key within block settings' support array indicating support for font style. */ -const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle'; +const FONT_STYLE_SUPPORT_KEY = 'typography.fontStyle'; /** * Key within block settings' support array indicating support for font weight. */ -const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight'; +const FONT_WEIGHT_SUPPORT_KEY = 'typography.fontWeight'; /** * Key within block settings' supports array indicating support for text * align e.g. settings found in `block.json`. @@ -34,7 +34,7 @@ const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns'; * Key within block settings' supports array indicating support for text * decorations e.g. settings found in `block.json`. */ -const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration'; +const TEXT_DECORATION_SUPPORT_KEY = 'typography.textDecoration'; /** * Key within block settings' supports array indicating support for writing mode * e.g. settings found in `block.json`. @@ -44,13 +44,13 @@ const WRITING_MODE_SUPPORT_KEY = 'typography.__experimentalWritingMode'; * Key within block settings' supports array indicating support for text * transforms e.g. settings found in `block.json`. */ -const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform'; +const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.textTransform'; /** * Key within block settings' supports array indicating support for letter-spacing * e.g. settings found in `block.json`. */ -const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing'; +const LETTER_SPACING_SUPPORT_KEY = 'typography.letterSpacing'; const LAYOUT_SUPPORT_KEY = 'layout'; const TYPOGRAPHY_SUPPORT_KEYS = [ LINE_HEIGHT_SUPPORT_KEY, diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index cf3f4327c8f034..ab9a464fe5efbc 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -27,12 +27,12 @@ function omit( object, keys ) { ); } -const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing'; -const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform'; -const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration'; +const LETTER_SPACING_SUPPORT_KEY = 'typography.letterSpacing'; +const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.textTransform'; +const TEXT_DECORATION_SUPPORT_KEY = 'typography.textDecoration'; const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns'; -const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle'; -const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight'; +const FONT_STYLE_SUPPORT_KEY = 'typography.fontStyle'; +const FONT_WEIGHT_SUPPORT_KEY = 'typography.fontWeight'; const WRITING_MODE_SUPPORT_KEY = 'typography.__experimentalWritingMode'; export const TYPOGRAPHY_SUPPORT_KEY = 'typography'; export const TYPOGRAPHY_SUPPORT_KEYS = [ diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 8116b5eecaebf0..82f0661d04e40f 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -238,7 +238,6 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) { /> { displayPostContent && ( [ + TYPOGRAPHY_SUPPORTS_EXPERIMENTAL_TO_STABLE[ key ] || key, + value, + ] ) + ); + } + + return supports; +} + /** * Takes the unprocessed block type settings, merges them with block type metadata * and applies all the existing filters for the registered block type. @@ -102,6 +124,9 @@ export const processBlockType = ), }; + // Stabilize any experimental supports before applying filters. + blockType.supports = stabilizeSupports( blockType.supports ); + const settings = applyFilters( 'blocks.registerBlockType', blockType, @@ -109,6 +134,10 @@ export const processBlockType = null ); + // Re-stabilize any experimental supports after applying filters. + // This ensures that any supports updated by filters are also stabilized. + blockType.supports = stabilizeSupports( blockType.supports ); + if ( settings.description && typeof settings.description !== 'string' @@ -119,29 +148,38 @@ export const processBlockType = } if ( settings.deprecated ) { - settings.deprecated = settings.deprecated.map( ( deprecation ) => - Object.fromEntries( - Object.entries( - // Only keep valid deprecation keys. - applyFilters( - 'blocks.registerBlockType', - // Merge deprecation keys with pre-filter settings - // so that filters that depend on specific keys being - // present don't fail. - { - // Omit deprecation keys here so that deprecations - // can opt out of specific keys like "supports". - ...omit( blockType, DEPRECATED_ENTRY_KEYS ), - ...deprecation, - }, - blockType.name, - deprecation - ) - ).filter( ( [ key ] ) => + settings.deprecated = settings.deprecated.map( ( deprecation ) => { + // Stabilize any experimental supports before applying filters. + deprecation.supports = stabilizeSupports( + deprecation.supports + ); + const filteredDeprecation = // Only keep valid deprecation keys. + applyFilters( + 'blocks.registerBlockType', + // Merge deprecation keys with pre-filter settings + // so that filters that depend on specific keys being + // present don't fail. + { + // Omit deprecation keys here so that deprecations + // can opt out of specific keys like "supports". + ...omit( blockType, DEPRECATED_ENTRY_KEYS ), + ...deprecation, + }, + blockType.name, + deprecation + ); + // Re-stabilize any experimental supports after applying filters. + // This ensures that any supports updated by filters are also stabilized. + filteredDeprecation.supports = stabilizeSupports( + filteredDeprecation.supports + ); + + return Object.fromEntries( + Object.entries( filteredDeprecation ).filter( ( [ key ] ) => DEPRECATED_ENTRY_KEYS.includes( key ) ) - ) - ); + ); + } ); } if ( ! isPlainObject( settings ) ) { diff --git a/packages/blocks/src/store/test/private-selectors.js b/packages/blocks/src/store/test/private-selectors.js index ada2bd7c8cbcfe..2c173b96b0bcb1 100644 --- a/packages/blocks/src/store/test/private-selectors.js +++ b/packages/blocks/src/store/test/private-selectors.js @@ -127,12 +127,12 @@ describe( 'private selectors', () => { name: 'core/example-block', supports: { typography: { - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalTextDecoration: true, - __experimentalTextTransform: true, - __experimentalLetterSpacing: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + textDecoration: true, + textTransform: true, + letterSpacing: true, fontSize: true, lineHeight: true, }, diff --git a/packages/blocks/src/store/test/process-block-type.js b/packages/blocks/src/store/test/process-block-type.js new file mode 100644 index 00000000000000..3c6838e311f778 --- /dev/null +++ b/packages/blocks/src/store/test/process-block-type.js @@ -0,0 +1,304 @@ +/** + * WordPress dependencies + */ +import { addFilter, removeFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import { processBlockType } from '../process-block-type'; + +describe( 'processBlockType', () => { + const baseBlockSettings = { + apiVersion: 3, + attributes: {}, + edit: () => null, + name: 'test/block', + save: () => null, + title: 'Test Block', + }; + + const select = { + getBootstrappedBlockType: () => null, + }; + + afterEach( () => { + removeFilter( 'blocks.registerBlockType', 'test/filterSupports' ); + } ); + + it( 'should return the block type with stabilized typography supports', () => { + const blockSettings = { + ...baseBlockSettings, + supports: { + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalLetterSpacing: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + }, + }; + + const processedBlockType = processBlockType( + 'test/block', + blockSettings + )( { select } ); + + expect( processedBlockType.supports.typography ).toEqual( { + fontSize: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + } ); + } ); + + it( 'should return the block type with stable typography supports', () => { + const blockSettings = { + ...baseBlockSettings, + supports: { + typography: { + fontSize: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + }, + }; + + const processedBlockType = processBlockType( + 'test/block', + blockSettings + )( { select } ); + + expect( processedBlockType.supports.typography ).toEqual( { + fontSize: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + } ); + } ); + + it( 'should reapply transformations after supports are filtered', () => { + const blockSettings = { + ...baseBlockSettings, + supports: { + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalLetterSpacing: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + }, + }; + + addFilter( + 'blocks.registerBlockType', + 'test/filterSupports', + ( settings, name ) => { + if ( name === 'test/block' && settings.supports.typography ) { + settings.supports.typography.__experimentalFontFamily = false; + settings.supports.typography.__experimentalFontStyle = false; + settings.supports.typography.__experimentalFontWeight = false; + } + return settings; + } + ); + + const processedBlockType = processBlockType( + 'test/block', + blockSettings + )( { select } ); + + expect( processedBlockType.supports.typography ).toEqual( { + fontSize: true, + lineHeight: true, + fontFamily: false, + fontStyle: false, + fontWeight: false, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + } ); + } ); + + it( 'should stabilize experimental typography supports within block deprecations', () => { + const blockSettings = { + ...baseBlockSettings, + supports: { + typography: { + fontSize: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + }, + deprecated: [ + { + supports: { + typography: { + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalLetterSpacing: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalWritingMode: true, + }, + }, + }, + ], + }; + + const processedBlockType = processBlockType( + 'test/block', + blockSettings + )( { select } ); + + expect( + processedBlockType.deprecated[ 0 ].supports.typography + ).toEqual( { + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + } ); + } ); + + it( 'should reapply transformations after supports are filtered within block deprecations', () => { + const blockSettings = { + ...baseBlockSettings, + supports: { + typography: { + fontSize: true, + lineHeight: true, + fontFamily: true, + fontStyle: true, + fontWeight: true, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + fontAppearance: true, + textTransform: true, + }, + }, + }, + deprecated: [ + { + supports: { + typography: { + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalLetterSpacing: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalWritingMode: true, + }, + }, + }, + ], + }; + + addFilter( + 'blocks.registerBlockType', + 'test/filterSupports', + ( settings, name ) => { + if ( name === 'test/block' && settings.supports.typography ) { + settings.supports.typography.__experimentalFontFamily = false; + settings.supports.typography.__experimentalFontStyle = false; + settings.supports.typography.__experimentalFontWeight = false; + } + return settings; + } + ); + + const processedBlockType = processBlockType( + 'test/block', + blockSettings + )( { select } ); + + expect( + processedBlockType.deprecated[ 0 ].supports.typography + ).toEqual( { + fontFamily: false, + fontStyle: false, + fontWeight: false, + letterSpacing: true, + textTransform: true, + textDecoration: true, + __experimentalWritingMode: true, + } ); + } ); +} ); diff --git a/packages/components/src/form-file-upload/index.tsx b/packages/components/src/form-file-upload/index.tsx index 0600e47d7324c3..66f0b2ea6d6480 100644 --- a/packages/components/src/form-file-upload/index.tsx +++ b/packages/components/src/form-file-upload/index.tsx @@ -47,6 +47,12 @@ export function FormFileUpload( { { children } ); + // @todo: Temporary fix a bug that prevents Chromium browsers from selecting ".heic" files + // from the file upload. See https://core.trac.wordpress.org/ticket/62268#comment:4. + // This can be removed once the Chromium fix is in the stable channel. + const compatAccept = !! accept?.includes( 'image/*' ) + ? `${ accept }, image/heic, image/heif` + : accept; return (
@@ -56,7 +62,7 @@ export function FormFileUpload( { ref={ ref } multiple={ multiple } style={ { display: 'none' } } - accept={ accept } + accept={ compatAccept } onChange={ onChange } onClick={ onClick } data-testid="form-file-upload-input" diff --git a/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js b/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js index 46719a00c16aad..7c88fee0d5b727 100644 --- a/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js +++ b/packages/edit-site/src/components/block-editor/use-editor-iframe-props.js @@ -60,11 +60,10 @@ export default function useEditorIframeProps() { } ); } }, - onClick: () => { + onClick: () => history.push( { ...params, canvas: 'edit' }, undefined, { transition: 'canvas-mode-edit-transition', - } ); - }, + } ), onClickCapture: ( event ) => { if ( currentPostIsTrashed ) { event.preventDefault(); diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 9ca88f40f1f001..2edea0fdbc3da3 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -19,7 +19,8 @@ import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; import { moreVertical } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, Fragment } from '@wordpress/element'; +import { usePrevious } from '@wordpress/compose'; /** * Internal dependencies @@ -291,18 +292,52 @@ function GlobalStylesEditorCanvasContainerLink() { }, [ editorCanvasContainerView, isRevisionsOpen, goTo ] ); } -function GlobalStylesUI() { +function useNavigatorSync( parentPath, onPathChange ) { + const navigator = useNavigator(); + const { path: childPath } = navigator.location; + const previousParentPath = usePrevious( parentPath ); + const previousChildPath = usePrevious( childPath ); + useEffect( () => { + if ( parentPath !== childPath ) { + if ( parentPath !== previousParentPath ) { + navigator.goTo( parentPath ); + } else if ( childPath !== previousChildPath ) { + onPathChange( childPath ); + } + } + }, [ + onPathChange, + parentPath, + previousChildPath, + previousParentPath, + childPath, + navigator, + ] ); +} + +// This component is used to wrap the hook in order to conditionally execute it +// when the parent component is used on controlled mode. +function NavigationSync( { path: parentPath, onPathChange, children } ) { + useNavigatorSync( parentPath, onPathChange ); + return children; +} + +function GlobalStylesUI( { path, onPathChange } ) { const blocks = getBlockTypes(); const editorCanvasContainerView = useSelect( ( select ) => unlock( select( editSiteStore ) ).getEditorCanvasContainerView(), [] ); + return ( + { path && onPathChange && ( + + ) } diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 551d1448fde5c9..cbc0a4661bf3e7 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -125,7 +125,12 @@ export default function Layout( { route } ) { isResizableFrameOversized } /> - + { areas.sidebar } diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js new file mode 100644 index 00000000000000..afa9f489dde22b --- /dev/null +++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js @@ -0,0 +1,145 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useMemo, useState } from '@wordpress/element'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useViewportMatch } from '@wordpress/compose'; +import { + Button, + privateApis as componentsPrivateApis, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import GlobalStylesUI from '../global-styles/ui'; +import Page from '../page'; +import { unlock } from '../../lock-unlock'; +import StyleBook from '../style-book'; +import { STYLE_BOOK_COLOR_GROUPS } from '../style-book/constants'; + +const { useLocation, useHistory } = unlock( routerPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); +const GLOBAL_STYLES_PATH_PREFIX = '/wp_global_styles'; + +const GlobalStylesPageActions = ( { + isStyleBookOpened, + setIsStyleBookOpened, +} ) => { + return ( + + { __( 'Preview' ) } + + } + > + setIsStyleBookOpened( true ) } + defaultChecked + > + { __( 'Style book' ) } + + { __( 'Preview blocks and styles.' ) } + + + setIsStyleBookOpened( false ) } + > + { __( 'Site' ) } + + { __( 'Preview your site.' ) } + + + + ); +}; + +export default function GlobalStylesUIWrapper() { + const { params } = useLocation(); + const history = useHistory(); + const { canvas = 'view' } = params; + const [ isStyleBookOpened, setIsStyleBookOpened ] = useState( false ); + const isMobileViewport = useViewportMatch( 'medium', '<' ); + const pathWithPrefix = params.path; + const [ path, onPathChange ] = useMemo( () => { + const processedPath = pathWithPrefix.substring( + GLOBAL_STYLES_PATH_PREFIX.length + ); + return [ + processedPath ? processedPath : '/', + ( newPath ) => { + history.push( { + path: + ! newPath || newPath === '/' + ? GLOBAL_STYLES_PATH_PREFIX + : `${ GLOBAL_STYLES_PATH_PREFIX }${ newPath }`, + } ); + }, + ]; + }, [ pathWithPrefix, history ] ); + + return ( + <> + + ) : null + } + className="edit-site-styles" + title={ __( 'Styles' ) } + > + + + { canvas === 'view' && isStyleBookOpened && ( + + // Match '/blocks/core%2Fbutton' and + // '/blocks/core%2Fbutton/typography', but not + // '/blocks/core%2Fbuttons'. + path === + `/wp_global_styles/blocks/${ encodeURIComponent( + blockName + ) }` || + path.startsWith( + `/wp_global_styles/blocks/${ encodeURIComponent( + blockName + ) }/` + ) + } + path={ path } + onSelect={ ( blockName ) => { + if ( + STYLE_BOOK_COLOR_GROUPS.find( + ( group ) => group.slug === blockName + ) + ) { + // Go to color palettes Global Styles. + onPathChange( '/colors/palette' ); + return; + } + + // Now go to the selected block. + onPathChange( + `/blocks/${ encodeURIComponent( blockName ) }` + ); + } } + /> + ) } + + ); +} diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss b/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss new file mode 100644 index 00000000000000..88aa9ddf0c1618 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/style.scss @@ -0,0 +1,35 @@ +.edit-site-styles .edit-site-page-content { + .edit-site-global-styles-screen-root { + box-shadow: none; + & > div > hr { + display: none; + } + } + .edit-site-global-styles-sidebar__navigator-provider { + .components-tools-panel { + border-top: none; + } + overflow-y: auto; + padding-left: 0; + padding-right: 0; + + .edit-site-global-styles-sidebar__navigator-screen { + padding-top: $grid-unit-15; + padding-left: $grid-unit-15; + padding-right: $grid-unit-15; + padding-bottom: $grid-unit-15; + outline: none; + } + } + .edit-site-page-header { + padding-left: $grid-unit-60; + padding-right: $grid-unit-60; + @container (max-width: 430px) { + padding-left: $grid-unit-30; + padding-right: $grid-unit-30; + } + } + .edit-site-sidebar-button { + color: $gray-900; + } +} diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss index 016027ef715a45..202de5300076c1 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss @@ -7,7 +7,7 @@ &:hover, &:focus, - &[aria-current] { + &[aria-current="true"] { color: $gray-200; background: $gray-800; @@ -16,7 +16,7 @@ } } - &[aria-current] { + &[aria-current="true"] { background: var(--wp-admin-theme-color); color: $white; } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js index 6579107a60e55f..3dc93ff4d4df63 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js @@ -2,12 +2,9 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { edit, seen } from '@wordpress/icons'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { useViewportMatch } from '@wordpress/compose'; import { useCallback } from '@wordpress/element'; -import { store as editorStore } from '@wordpress/editor'; import { store as preferencesStore } from '@wordpress/preferences'; import { privateApis as routerPrivateApis } from '@wordpress/router'; @@ -17,18 +14,14 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; import { unlock } from '../../lock-unlock'; import { store as editSiteStore } from '../../store'; -import SidebarButton from '../sidebar-button'; import SidebarNavigationItem from '../sidebar-navigation-item'; -import StyleBook from '../style-book'; import useGlobalStylesRevisions from '../global-styles/screen-revisions/use-global-styles-revisions'; import SidebarNavigationScreenDetailsFooter from '../sidebar-navigation-screen-details-footer'; -import SidebarNavigationScreenGlobalStylesContent from './content'; +import { MainSidebarNavigationContent } from '../sidebar-navigation-screen-main'; const { useLocation, useHistory } = unlock( routerPrivateApis ); export function SidebarNavigationItemGlobalStyles( props ) { - const { openGeneralSidebar } = useDispatch( editSiteStore ); - const history = useHistory(); const { params } = useLocation(); const hasGlobalStyleVariations = useSelect( ( select ) => @@ -43,47 +36,25 @@ export function SidebarNavigationItemGlobalStyles( props ) { { ...props } params={ { path: '/wp_global_styles' } } uid="global-styles-navigation-item" + aria-current={ + params.path && params.path.startsWith( '/wp_global_styles' ) + } /> ); } - return ( - { - // Switch to edit mode. - history.push( - { - ...params, - canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - // Open global styles sidebar. - openGeneralSidebar( 'edit-site/global-styles' ); - } } - /> - ); + return ; } -export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { +export default function SidebarNavigationScreenGlobalStyles() { const history = useHistory(); const { params } = useLocation(); - const { canvas = 'view' } = params; const { revisions, isLoading: isLoadingRevisions } = useGlobalStylesRevisions(); const { openGeneralSidebar } = useDispatch( editSiteStore ); - const { setIsListViewOpened } = useDispatch( editorStore ); - const isMobileViewport = useViewportMatch( 'medium', '<' ); const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) ); - const { isStyleBookOpened, revisionsCount } = useSelect( ( select ) => { - const { getEditorCanvasContainerView } = unlock( - select( editSiteStore ) - ); + const { revisionsCount } = useSelect( ( select ) => { const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = select( coreStore ); const globalStylesId = __experimentalGetCurrentGlobalStylesId(); @@ -91,7 +62,6 @@ export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) : undefined; return { - isStyleBookOpened: 'style-book' === getEditorCanvasContainerView(), revisionsCount: globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0, }; @@ -115,19 +85,6 @@ export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { ] ); }, [ history, params, openGeneralSidebar, setPreference ] ); - const openStyleBook = useCallback( async () => { - await openGlobalStyles(); - // Open the Style Book once the canvas mode is set to edit, - // and the global styles sidebar is open. This ensures that - // the Style Book is not prematurely closed. - setEditorCanvasContainerView( 'style-book' ); - setIsListViewOpened( false ); - }, [ - openGlobalStyles, - setEditorCanvasContainerView, - setIsListViewOpened, - ] ); - const openRevisions = useCallback( async () => { await openGlobalStyles(); // Open the global styles revisions once the canvas mode is set to edit, @@ -142,16 +99,17 @@ export default function SidebarNavigationScreenGlobalStyles( { backPath } ) { const modifiedDateTime = revisions?.[ 0 ]?.modified; const shouldShowGlobalStylesFooter = hasRevisions && ! isLoadingRevisions && modifiedDateTime; - return ( <> } + content={ + + } footer={ shouldShowGlobalStylesFooter && ( ) } - actions={ - <> - { ! isMobileViewport && ( - - setEditorCanvasContainerView( - ! isStyleBookOpened - ? 'style-book' - : undefined - ) - } - isPressed={ isStyleBookOpened } - /> - ) } - await openGlobalStyles() } - /> - - } /> - { isStyleBookOpened && ! isMobileViewport && canvas === 'view' && ( - false } - onClick={ openStyleBook } - onSelect={ openStyleBook } - showCloseButton={ false } - showTabs={ false } - /> - ) } ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js index bdfb6ac93b51c4..49e60d44047326 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js @@ -21,6 +21,51 @@ import { PATTERN_TYPES, } from '../../utils/constants'; +export function MainSidebarNavigationContent() { + return ( + + + { __( 'Navigation' ) } + + + { __( 'Styles' ) } + + + { __( 'Pages' ) } + + + { __( 'Templates' ) } + + + { __( 'Patterns' ) } + + + ); +} + export default function SidebarNavigationScreenMain() { const { setEditorCanvasContainerView } = unlock( useDispatch( editSiteStore ) @@ -38,51 +83,7 @@ export default function SidebarNavigationScreenMain() { description={ __( 'Customize the appearance of your website using the block editor.' ) } - content={ - <> - - - { __( 'Navigation' ) } - - - { __( 'Styles' ) } - - - { __( 'Pages' ) } - - - { __( 'Templates' ) } - - - { __( 'Patterns' ) } - - - - } + content={ } /> ); } diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 84820952e1b621..7ecd24719a47bf 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -55,7 +55,7 @@ function createNavState() { }; } -function SidebarContentWrapper( { children } ) { +function SidebarContentWrapper( { children, shouldAnimate } ) { const navState = useContext( SidebarNavigationContext ); const wrapperRef = useRef(); const [ navAnimation, setNavAnimation ] = useState( null ); @@ -66,10 +66,19 @@ function SidebarContentWrapper( { children } ) { setNavAnimation( direction ); }, [ navState ] ); - const wrapperCls = clsx( 'edit-site-sidebar__screen-wrapper', { - 'slide-from-left': navAnimation === 'back', - 'slide-from-right': navAnimation === 'forward', - } ); + const wrapperCls = clsx( + 'edit-site-sidebar__screen-wrapper', + /* + * Some panes do not have sub-panes and therefore + * should not animate when clicked on. + */ + shouldAnimate + ? { + 'slide-from-left': navAnimation === 'back', + 'slide-from-right': navAnimation === 'forward', + } + : {} + ); return (
@@ -78,13 +87,20 @@ function SidebarContentWrapper( { children } ) { ); } -export default function SidebarContent( { routeKey, children } ) { +export default function SidebarContent( { + routeKey, + shouldAnimate, + children, +} ) { const [ navState ] = useState( createNavState ); return (
- + { children }
diff --git a/packages/edit-site/src/components/site-editor-routes/styles-edit.js b/packages/edit-site/src/components/site-editor-routes/styles-edit.js index ff52b957bc3609..e8225a8f526ebd 100644 --- a/packages/edit-site/src/components/site-editor-routes/styles-edit.js +++ b/packages/edit-site/src/components/site-editor-routes/styles-edit.js @@ -3,15 +3,24 @@ */ import Editor from '../editor'; import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; +import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; export const stylesEditRoute = { name: 'styles-edit', match: ( params ) => { - return params.path === '/wp_global_styles' && params.canvas === 'edit'; + return ( + params.path && + params.path.startsWith( '/wp_global_styles' ) && + params.canvas !== 'edit' + ); }, areas: { + content: , sidebar: , preview: , mobile: , }, + widths: { + content: 380, + }, }; diff --git a/packages/edit-site/src/components/site-editor-routes/styles-view.js b/packages/edit-site/src/components/site-editor-routes/styles-view.js index 856a610eb23677..cc9411eb8144c0 100644 --- a/packages/edit-site/src/components/site-editor-routes/styles-view.js +++ b/packages/edit-site/src/components/site-editor-routes/styles-view.js @@ -3,14 +3,24 @@ */ import Editor from '../editor'; import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; +import GlobalStylesUIWrapper from '../sidebar-global-styles-wrapper'; export const stylesViewRoute = { name: 'styles-view', match: ( params ) => { - return params.path === '/wp_global_styles' && params.canvas !== 'edit'; + return ( + params.path && + params.path.startsWith( '/wp_global_styles' ) && + params.canvas !== 'edit' + ); }, areas: { + content: , sidebar: , preview: , + mobile: , + }, + widths: { + content: 380, }, }; diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index e9660323b83734..9918c169ff6ab0 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -24,7 +24,14 @@ import { import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { useSelect } from '@wordpress/data'; import { useResizeObserver } from '@wordpress/compose'; -import { useMemo, useState, memo, useContext } from '@wordpress/element'; +import { + useMemo, + useState, + memo, + useContext, + useRef, + useLayoutEffect, +} from '@wordpress/element'; import { ENTER, SPACE } from '@wordpress/keycodes'; /** @@ -53,6 +60,48 @@ function isObjectEmpty( object ) { return ! object || Object.keys( object ).length === 0; } +/** + * Scrolls to a section within an iframe. + * + * @param {string} anchorId The id of the element to scroll to. + * @param {HTMLIFrameElement} iframe The target iframe. + */ +const scrollToSection = ( anchorId, iframe ) => { + if ( ! iframe || ! iframe?.contentDocument ) { + return; + } + + const element = iframe.contentDocument.getElementById( anchorId ); + if ( element ) { + element.scrollIntoView( { + behavior: 'smooth', + } ); + } +}; + +/** + * Parses a Block Editor navigation path to extract the block name and + * build a style book navigation path. The object can be extended to include a category, + * representing a style book tab/section. + * + * @param {string} path An internal Block Editor navigation path. + * @return {null|{block: string}} An object containing the example to navigate to. + */ +const getStyleBookNavigationFromPath = ( path ) => { + if ( path && typeof path === 'string' ) { + let block = path.includes( '/blocks/' ) + ? decodeURIComponent( path.split( '/blocks/' )[ 1 ] ) + : null; + // Default to theme-colors if the path ends with /colors. + block = path.endsWith( '/colors' ) ? 'theme-colors' : block; + + return { + block, + }; + } + return null; +}; + /** * Retrieves colors, gradients, and duotone filters from Global Styles. * The inclusion of default (Core) palettes is controlled by the relevant @@ -137,6 +186,7 @@ function StyleBook( { onClose, showTabs = true, userConfig = {}, + path = '', } ) { const [ resizeObserver, sizes ] = useResizeObserver(); const [ textColor ] = useGlobalStyle( 'color.text' ); @@ -154,6 +204,7 @@ function StyleBook( { ); const { base: baseConfig } = useContext( GlobalStylesContext ); + const goTo = getStyleBookNavigationFromPath( path ); const mergedConfig = useMemo( () => { if ( ! isObjectEmpty( userConfig ) && ! isObjectEmpty( baseConfig ) ) { @@ -228,6 +279,7 @@ function StyleBook( { settings={ settings } sizes={ sizes } title={ tab.title } + goTo={ goTo } /> ) ) } @@ -240,6 +292,7 @@ function StyleBook( { onSelect={ onSelect } settings={ settings } sizes={ sizes } + goTo={ goTo } /> ) }
@@ -256,9 +309,11 @@ const StyleBookBody = ( { settings, sizes, title, + goTo, } ) => { const [ isFocused, setIsFocused ] = useState( false ); - + const [ hasIframeLoaded, setHasIframeLoaded ] = useState( false ); + const iframeRef = useRef( null ); // The presence of an `onClick` prop indicates that the Style Book is being used as a button. // In this case, add additional props to the iframe to make it behave like a button. const buttonModeProps = { @@ -287,8 +342,17 @@ const StyleBookBody = ( { readonly: true, }; + const handleLoad = () => setHasIframeLoaded( true ); + useLayoutEffect( () => { + if ( goTo?.block && hasIframeLoaded && iframeRef?.current ) { + scrollToSection( `example-${ goTo?.block }`, iframeRef?.current ); + } + }, [ iframeRef?.current, goTo?.block, scrollToSection, hasIframeLoaded ] ); + return (