From 877628cb8745930715379f41d012ccbc7b23cf13 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Thu, 25 Jul 2024 15:41:41 +0400 Subject: [PATCH 01/26] Fix item font family item height in the sidebar (#63125) * fix item font family item height * Apply size="large" to custom fonts as well --------- Co-authored-by: Sarah Norris Co-authored-by: matiasbenedetto Co-authored-by: mikachan Co-authored-by: tyxla Co-authored-by: jasmussen Co-authored-by: ciampo Co-authored-by: t-hamano --- .../edit-site/src/components/global-styles/font-families.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/font-families.js b/packages/edit-site/src/components/global-styles/font-families.js index 7a05a66c6efdfe..f6610d0457bbae 100644 --- a/packages/edit-site/src/components/global-styles/font-families.js +++ b/packages/edit-site/src/components/global-styles/font-families.js @@ -69,7 +69,7 @@ function FontFamilies() { _x( 'Theme', 'font source' ) } - + { themeFonts.map( ( font ) => ( - + { customFonts.map( ( font ) => ( Date: Thu, 25 Jul 2024 13:54:17 +0100 Subject: [PATCH 02/26] Zoom Out: Use the block editor for insertion point data (#63934) * Zoom Out: Use the block editor for insertion point data * remove unlock Co-authored-by: scruffian Co-authored-by: MaggieCabrera Co-authored-by: Mamaduka --- .../block-tools/zoom-out-mode-inserters.js | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js index 5d273a4f3f6d58..45b1cf0f64bd7d 100644 --- a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js +++ b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; /** @@ -16,8 +16,9 @@ function ZoomOutModeInserters() { const [ isReady, setIsReady ] = useState( false ); const { hasSelection, + blockInsertionPoint, blockOrder, - insertionPoint, + blockInsertionPointVisible, setInserterIsOpened, sectionRootClientId, selectedBlockClientId, @@ -25,23 +26,19 @@ function ZoomOutModeInserters() { } = useSelect( ( select ) => { const { getSettings, + getBlockInsertionPoint, getBlockOrder, getSelectionStart, getSelectedBlockClientId, getHoveredBlockClientId, + isBlockInsertionPointVisible, } = select( blockEditorStore ); const { sectionRootClientId: root } = unlock( getSettings() ); - // To do: move ZoomOutModeInserters to core/editor. - // Or we perhaps we should move the insertion point state to the - // block-editor store. I'm not sure what it was ever moved to the editor - // store, because all the inserter components all live in the - // block-editor package. - // eslint-disable-next-line @wordpress/data-no-store-string-literals - const editor = select( 'core/editor' ); return { hasSelection: !! getSelectionStart().clientId, + blockInsertionPoint: getBlockInsertionPoint(), blockOrder: getBlockOrder( root ), - insertionPoint: unlock( editor ).getInsertionPoint(), + blockInsertionPointVisible: isBlockInsertionPointVisible(), sectionRootClientId: root, setInserterIsOpened: getSettings().__experimentalSetIsInserterOpened, @@ -50,6 +47,8 @@ function ZoomOutModeInserters() { }; }, [] ); + const blockEditorDispatch = useDispatch( blockEditorStore ); + // Defer the initial rendering to avoid the jumps due to the animation. useEffect( () => { const timeout = setTimeout( () => { @@ -65,14 +64,8 @@ function ZoomOutModeInserters() { } return [ undefined, ...blockOrder ].map( ( clientId, index ) => { - const shouldRenderInserter = insertionPoint.insertionIndex !== index; - const shouldRenderInsertionPoint = - insertionPoint.insertionIndex === index; - - if ( ! shouldRenderInserter && ! shouldRenderInsertionPoint ) { - return null; - } + blockInsertionPointVisible && blockInsertionPoint.index === index; const previousClientId = clientId; const nextClientId = blockOrder[ index ]; @@ -86,6 +79,8 @@ function ZoomOutModeInserters() { hoveredBlockClientId === previousClientId || hoveredBlockClientId === nextClientId; + const { showInsertionPoint } = blockEditorDispatch; + return ( ) } - { shouldRenderInserter && ( + { ! shouldRenderInsertionPoint && ( { @@ -114,6 +109,9 @@ function ZoomOutModeInserters() { tab: 'patterns', category: 'all', } ); + showInsertionPoint( sectionRootClientId, index, { + operation: 'insert', + } ); } } /> ) } From 4af798f2b4342e4f157103e528309e2d3a1cc572 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 25 Jul 2024 14:02:50 +0100 Subject: [PATCH 03/26] Global Styles: Add a typesets section to Typography (#62539) * Global Styles: Add a typesets section to Typography Only show typesets if there are fonts * update typesets to contain font families * add variation name to typesets * add variation name to typesets * use variation titles for typeset button * Update packages/edit-site/src/components/global-styles/typeset-button.js Co-authored-by: Sarah Norris <1645628+mikachan@users.noreply.github.com> * Update packages/edit-site/src/components/global-styles/typeset-button.js Co-authored-by: Sarah Norris <1645628+mikachan@users.noreply.github.com> * Update packages/edit-site/src/components/global-styles/typeset-button.js Co-authored-by: Sarah Norris <1645628+mikachan@users.noreply.github.com> * Add fontLibraryEnabled to ScreenTypography * Remove window.__experimentalDisableFontLibrary * Check for themeFontFamilies and customFontFamilies --------- Co-authored-by: Sarah Norris <1645628+mikachan@users.noreply.github.com> Co-authored-by: Sarah Norris Co-authored-by: scruffian Co-authored-by: mikachan Co-authored-by: jasmussen Co-authored-by: richtabor --- .../global-styles/screen-typeset.js | 42 +++++++++ .../global-styles/screen-typography.js | 8 +- .../global-styles/typeset-button.js | 93 +++++++++++++++++++ .../src/components/global-styles/typeset.js | 73 +++++++++++++++ .../src/components/global-styles/ui.js | 5 + .../src/components/global-styles/utils.js | 14 ++- 6 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 packages/edit-site/src/components/global-styles/screen-typeset.js create mode 100644 packages/edit-site/src/components/global-styles/typeset-button.js create mode 100644 packages/edit-site/src/components/global-styles/typeset.js diff --git a/packages/edit-site/src/components/global-styles/screen-typeset.js b/packages/edit-site/src/components/global-styles/screen-typeset.js new file mode 100644 index 00000000000000..ce754121dfe1b5 --- /dev/null +++ b/packages/edit-site/src/components/global-styles/screen-typeset.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { __experimentalVStack as VStack } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import TypographyVariations from './variations/variations-typography'; +import ScreenHeader from './header'; +import FontFamilies from './font-families'; + +function ScreenTypeset() { + const fontLibraryEnabled = useSelect( + ( select ) => + select( editorStore ).getEditorSettings().fontLibraryEnabled, + [] + ); + + return ( + <> + +
+ + + + { fontLibraryEnabled && } + +
+ + ); +} + +export default ScreenTypeset; diff --git a/packages/edit-site/src/components/global-styles/screen-typography.js b/packages/edit-site/src/components/global-styles/screen-typography.js index a58802a204ce32..c23592c51a6a2a 100644 --- a/packages/edit-site/src/components/global-styles/screen-typography.js +++ b/packages/edit-site/src/components/global-styles/screen-typography.js @@ -3,17 +3,17 @@ */ import { __ } from '@wordpress/i18n'; import { __experimentalVStack as VStack } from '@wordpress/components'; -import { store as editorStore } from '@wordpress/editor'; import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies */ import TypographyElements from './typography-elements'; -import TypographyVariations from './variations/variations-typography'; -import FontFamilies from './font-families'; import ScreenHeader from './header'; import FontSizesCount from './font-sizes/font-sizes-count'; +import TypesetButton from './typeset-button'; +import FontFamilies from './font-families'; function ScreenTypography() { const fontLibraryEnabled = useSelect( @@ -32,9 +32,9 @@ function ScreenTypography() { />
+ { fontLibraryEnabled && } -
diff --git a/packages/edit-site/src/components/global-styles/typeset-button.js b/packages/edit-site/src/components/global-styles/typeset-button.js new file mode 100644 index 00000000000000..d66310f2ed8ff5 --- /dev/null +++ b/packages/edit-site/src/components/global-styles/typeset-button.js @@ -0,0 +1,93 @@ +/** + * WordPress dependencies + */ +import { isRTL, __ } from '@wordpress/i18n'; +import { + __experimentalItemGroup as ItemGroup, + __experimentalVStack as VStack, + __experimentalHStack as HStack, + FlexItem, +} from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; +import { useMemo, useContext } from '@wordpress/element'; +import { Icon, chevronLeft, chevronRight } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import FontLibraryProvider from './font-library-modal/context'; +import { getFontFamilies } from './utils'; +import { NavigationButtonAsItem } from './navigation-button'; +import Subtitle from './subtitle'; +import { unlock } from '../../lock-unlock'; +import { filterObjectByProperties } from '../../hooks/use-theme-style-variations/use-theme-style-variations-by-property'; + +const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); +const { mergeBaseAndUserConfigs } = unlock( editorPrivateApis ); + +function TypesetButton() { + const { base } = useContext( GlobalStylesContext ); + const { user: userConfig } = useContext( GlobalStylesContext ); + const config = mergeBaseAndUserConfigs( base, userConfig ); + const allFontFamilies = getFontFamilies( config ); + const hasFonts = + allFontFamilies.filter( ( font ) => font !== null ).length > 0; + const variations = useSelect( ( select ) => { + return select( + coreStore + ).__experimentalGetCurrentThemeGlobalStylesVariations(); + }, [] ); + const userTypographyConfig = filterObjectByProperties( + userConfig, + 'typography' + ); + + const title = useMemo( () => { + if ( Object.keys( userTypographyConfig ).length === 0 ) { + return __( 'Default' ); + } + const activeVariation = variations.find( ( variation ) => { + return ( + JSON.stringify( + filterObjectByProperties( variation, 'typography' ) + ) === JSON.stringify( userTypographyConfig ) + ); + } ); + if ( activeVariation ) { + return activeVariation.title; + } + return allFontFamilies.map( ( font ) => font?.name ).join( ', ' ); + }, [ userTypographyConfig, variations ] ); + + return ( + hasFonts && ( + + + { __( 'Typeset' ) } + + + + + { title } + + + + + + ) + ); +} + +export default ( { ...props } ) => ( + + + +); diff --git a/packages/edit-site/src/components/global-styles/typeset.js b/packages/edit-site/src/components/global-styles/typeset.js new file mode 100644 index 00000000000000..e99e6a037500ad --- /dev/null +++ b/packages/edit-site/src/components/global-styles/typeset.js @@ -0,0 +1,73 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + __experimentalItemGroup as ItemGroup, + __experimentalVStack as VStack, + __experimentalHStack as HStack, +} from '@wordpress/components'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import FontLibraryProvider, { + FontLibraryContext, +} from './font-library-modal/context'; +import FontLibraryModal from './font-library-modal'; +import FontFamilyItem from './font-family-item'; +import Subtitle from './subtitle'; +import { getFontFamilies } from './utils'; +import { unlock } from '../../lock-unlock'; + +const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); +const { mergeBaseAndUserConfigs } = unlock( editorPrivateApis ); + +function Typesets() { + const { modalTabOpen, setModalTabOpen } = useContext( FontLibraryContext ); + const { base } = useContext( GlobalStylesContext ); + const { user: userConfig } = useContext( GlobalStylesContext ); + const config = mergeBaseAndUserConfigs( base, userConfig ); + const allFontFamilies = getFontFamilies( config ); + const hasFonts = + allFontFamilies.filter( ( font ) => font !== null ).length > 0; + + return ( + hasFonts && ( + <> + { !! modalTabOpen && ( + setModalTabOpen( null ) } + defaultTabId={ modalTabOpen } + /> + ) } + + + + { __( 'Fonts' ) } + + + { allFontFamilies.map( + ( font ) => + font && ( + + ) + ) } + + + + ) + ); +} + +export default ( { ...props } ) => ( + + + +); diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 40d20bc1ec86f8..54bd4f97390a8f 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -32,6 +32,7 @@ import { } from './screen-block-list'; import ScreenBlock from './screen-block'; import ScreenTypography from './screen-typography'; +import ScreenTypeset from './screen-typeset'; import ScreenTypographyElement from './screen-typography-element'; import FontSize from './font-sizes/font-size'; import FontSizes from './font-sizes/font-sizes'; @@ -323,6 +324,10 @@ function GlobalStylesUI() { + + + + diff --git a/packages/edit-site/src/components/global-styles/utils.js b/packages/edit-site/src/components/global-styles/utils.js index 6096b381fb2187..66a25854a06fe1 100644 --- a/packages/edit-site/src/components/global-styles/utils.js +++ b/packages/edit-site/src/components/global-styles/utils.js @@ -52,7 +52,19 @@ function getFontFamilyFromSetting( fontFamilies, setting ) { } export function getFontFamilies( themeJson ) { - const fontFamilies = themeJson?.settings?.typography?.fontFamilies?.theme; // TODO this could not be under theme. + const themeFontFamilies = + themeJson?.settings?.typography?.fontFamilies?.theme; + const customFontFamilies = + themeJson?.settings?.typography?.fontFamilies?.custom; + + let fontFamilies = []; + if ( themeFontFamilies && customFontFamilies ) { + fontFamilies = [ ...themeFontFamilies, ...customFontFamilies ]; + } else if ( themeFontFamilies ) { + fontFamilies = themeFontFamilies; + } else if ( customFontFamilies ) { + fontFamilies = customFontFamilies; + } const bodyFontFamilySetting = themeJson?.styles?.typography?.fontFamily; const bodyFontFamily = getFontFamilyFromSetting( fontFamilies, From 3336ee9bc3490dee6d6d843a8a83229df33bfccd Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 25 Jul 2024 17:04:10 +0400 Subject: [PATCH 04/26] Zoom out: Get store action outside the loop (#63936) Co-authored-by: Mamaduka --- .../src/components/block-tools/zoom-out-mode-inserters.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js index 45b1cf0f64bd7d..bb044f9479c024 100644 --- a/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js +++ b/packages/block-editor/src/components/block-tools/zoom-out-mode-inserters.js @@ -47,7 +47,7 @@ function ZoomOutModeInserters() { }; }, [] ); - const blockEditorDispatch = useDispatch( blockEditorStore ); + const { showInsertionPoint } = useDispatch( blockEditorStore ); // Defer the initial rendering to avoid the jumps due to the animation. useEffect( () => { @@ -79,8 +79,6 @@ function ZoomOutModeInserters() { hoveredBlockClientId === previousClientId || hoveredBlockClientId === nextClientId; - const { showInsertionPoint } = blockEditorDispatch; - return ( Date: Thu, 25 Jul 2024 15:23:08 +0100 Subject: [PATCH 05/26] Fix toggle help indentation (#63903) Co-authored-by: jameskoster Co-authored-by: ciampo Co-authored-by: tyxla Co-authored-by: paaljoachim Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: richtabor Co-authored-by: afercia --- packages/components/CHANGELOG.md | 1 + packages/components/src/toggle-control/style.scss | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 4a423eeb4a57df..6c32534598fb5c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -17,6 +17,7 @@ - `ComboboxControl`: Fix ComboboxControl reset button when using the keyboard. ([#63410](https://github.com/WordPress/gutenberg/pull/63410)) - `Button`: Never apply `aria-disabled` to anchor ([#63376](https://github.com/WordPress/gutenberg/pull/63376)). - `SelectControl`: Fix hover/focus color in wp-admin ([#63855](https://github.com/WordPress/gutenberg/pull/63855)). +- `ToggleControl`: Fix indentation ([#63903](https://github.com/WordPress/gutenberg/pull/63903)). ### Enhancements diff --git a/packages/components/src/toggle-control/style.scss b/packages/components/src/toggle-control/style.scss index 263a372b00fe84..68733b53a9bc94 100644 --- a/packages/components/src/toggle-control/style.scss +++ b/packages/components/src/toggle-control/style.scss @@ -7,5 +7,6 @@ } .components-toggle-control__help { - margin-left: $toggle-width + $grid-unit-10; + display: inline-block; + margin-inline-start: $toggle-width + $grid-unit-10; } From e434ec4bdce19d46d610bfd9d28924bdbe095a7e Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:54:54 +0900 Subject: [PATCH 06/26] .wp-env.json schema: Fix schema and add unit tests (#63281) Co-authored-by: t-hamano Co-authored-by: ajlende --- schemas/json/wp-env.json | 4 ++- test/integration/wp-env-schema.test.js | 38 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/integration/wp-env-schema.test.js diff --git a/schemas/json/wp-env.json b/schemas/json/wp-env.json index 9208ff8e0661f2..491d1f8cf73017 100644 --- a/schemas/json/wp-env.json +++ b/schemas/json/wp-env.json @@ -82,6 +82,7 @@ "$ref": "#/definitions/wpEnvProperties" }, { + "type": "object", "properties": { "$schema": { "type": "string" @@ -91,10 +92,10 @@ "type": "object", "patternProperties": { "[a-zA-Z]": { - "type": "object", "allOf": [ { "$ref": "#/definitions/wpEnvProperties" }, { + "type": "object", "propertyNames": { "$ref": "#/definitions/wpEnvPropertyNames" } @@ -107,6 +108,7 @@ } }, { + "type": "object", "propertyNames": { "anyOf": [ { diff --git a/test/integration/wp-env-schema.test.js b/test/integration/wp-env-schema.test.js new file mode 100644 index 00000000000000..48ef039a8fb0d5 --- /dev/null +++ b/test/integration/wp-env-schema.test.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import Ajv from 'ajv'; + +/** + * Internal dependencies + */ +import wpEnvSchema from '../../schemas/json/wp-env.json'; +import wpEnvJsonFile from '../../.wp-env.json'; + +describe( '.wp-env.json schema', () => { + const ajv = new Ajv( { + allowMatchingProperties: true, + } ); + + test( 'strictly adheres to the draft-07 meta schema', () => { + // Use ajv.compile instead of ajv.validateSchema to validate the schema + // because validateSchema only checks syntax, whereas, compile checks + // if the schema is semantically correct with strict mode. + // See https://github.com/ajv-validator/ajv/issues/1434#issuecomment-822982571 + const result = ajv.compile( wpEnvSchema ); + + expect( result.errors ).toBe( null ); + } ); + + test( 'validates schema for .wp-env.json', () => { + // We want to validate the .wp-env.json file using the local schema. + const { $schema, ...metadata } = wpEnvJsonFile; + + // we expect the $schema property to be present in the .wp-env.json file + expect( $schema ).toBeTruthy(); + + const result = ajv.validate( wpEnvSchema, metadata ) || ajv.errors; + + expect( result ).toBe( true ); + } ); +} ); From a5cb97671f9f4463fdb532585ed47c295298abd3 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 25 Jul 2024 17:16:40 +0200 Subject: [PATCH 07/26] DataViews: Optimize the templates dataviews by extracting the fields definition (#63929) Co-authored-by: youknowriad Co-authored-by: ellatrix Co-authored-by: jameskoster --- .../src/components/page-templates/fields.js | 157 ++++++++++++++++ .../src/components/page-templates/index.js | 173 ++---------------- .../src/components/page-templates/style.scss | 6 +- 3 files changed, 172 insertions(+), 164 deletions(-) create mode 100644 packages/edit-site/src/components/page-templates/fields.js diff --git a/packages/edit-site/src/components/page-templates/fields.js b/packages/edit-site/src/components/page-templates/fields.js new file mode 100644 index 00000000000000..d26f1906a10664 --- /dev/null +++ b/packages/edit-site/src/components/page-templates/fields.js @@ -0,0 +1,157 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + +/** + * WordPress dependencies + */ +import { Icon, __experimentalHStack as HStack } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useState, useMemo } from '@wordpress/element'; +import { decodeEntities } from '@wordpress/html-entities'; +import { parse } from '@wordpress/blocks'; +import { + BlockPreview, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { EditorProvider } from '@wordpress/editor'; + +/** + * Internal dependencies + */ +import { Async } from '../async'; +import { default as Link, useLink } from '../routes/link'; +import { useAddedBy } from './hooks'; + +import usePatternSettings from '../page-patterns/use-pattern-settings'; +import { unlock } from '../../lock-unlock'; + +const { useGlobalStyle } = unlock( blockEditorPrivateApis ); + +function PreviewField( { item } ) { + const settings = usePatternSettings(); + const [ backgroundColor = 'white' ] = useGlobalStyle( 'color.background' ); + const blocks = useMemo( () => { + return parse( item.content.raw ); + }, [ item.content.raw ] ); + const { onClick } = useLink( { + postId: item.id, + postType: item.type, + canvas: 'edit', + } ); + + const isEmpty = ! blocks?.length; + // Wrap everything in a block editor provider to ensure 'styles' that are needed + // for the previews are synced between the site editor store and the block editor store. + // Additionally we need to have the `__experimentalBlockPatterns` setting in order to + // render patterns inside the previews. + // TODO: Same approach is used in the patterns list and it becomes obvious that some of + // the block editor settings are needed in context where we don't have the block editor. + // Explore how we can solve this in a better way. + return ( + +
+ +
+
+ ); +} + +export const previewField = { + label: __( 'Preview' ), + id: 'preview', + render: PreviewField, + enableSorting: false, +}; + +function TitleField( { item } ) { + const linkProps = { + params: { + postId: item.id, + postType: item.type, + canvas: 'edit', + }, + }; + return ( + + { decodeEntities( item.title?.rendered ) || __( '(no title)' ) } + + ); +} + +export const titleField = { + label: __( 'Template' ), + id: 'title', + getValue: ( { item } ) => item.title?.rendered, + render: TitleField, + enableHiding: false, + enableGlobalSearch: true, +}; + +export const descriptionField = { + label: __( 'Description' ), + id: 'description', + render: ( { item } ) => { + return ( + item.description && ( + + { decodeEntities( item.description ) } + + ) + ); + }, + enableSorting: false, + enableGlobalSearch: true, +}; + +function AuthorField( { item } ) { + const [ isImageLoaded, setIsImageLoaded ] = useState( false ); + const { text, icon, imageUrl } = useAddedBy( item.type, item.id ); + + return ( + + { imageUrl && ( +
+ setIsImageLoaded( true ) } + alt="" + src={ imageUrl } + /> +
+ ) } + { ! imageUrl && ( +
+ +
+ ) } + { text } +
+ ); +} + +export const authorField = { + label: __( 'Author' ), + id: 'author', + getValue: ( { item } ) => item.author_text, + render: AuthorField, +}; diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 9ec87ee8cca352..9c5db8fb1699d5 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -1,36 +1,18 @@ -/** - * External dependencies - */ -import clsx from 'clsx'; - /** * WordPress dependencies */ -import { Icon, __experimentalHStack as HStack } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; import { useEntityRecords } from '@wordpress/core-data'; -import { decodeEntities } from '@wordpress/html-entities'; -import { parse } from '@wordpress/blocks'; -import { - BlockPreview, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { - privateApis as editorPrivateApis, - EditorProvider, -} from '@wordpress/editor'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ -import { Async } from '../async'; import Page from '../page'; -import { default as Link, useLink } from '../routes/link'; import AddNewTemplate from '../add-new-template'; -import { useAddedBy } from './hooks'; import { TEMPLATE_POST_TYPE, OPERATOR_IS_ANY, @@ -38,14 +20,16 @@ import { LAYOUT_TABLE, LAYOUT_LIST, } from '../../utils/constants'; - -import usePatternSettings from '../page-patterns/use-pattern-settings'; import { unlock } from '../../lock-unlock'; import { useEditPostAction } from '../dataviews-actions'; +import { + authorField, + descriptionField, + previewField, + titleField, +} from './fields'; const { usePostActions } = unlock( editorPrivateApis ); - -const { useGlobalStyle } = unlock( blockEditorPrivateApis ); const { useHistory, useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; @@ -109,104 +93,6 @@ const DEFAULT_VIEW = { filters: [], }; -function Title( { item, viewType } ) { - if ( viewType === LAYOUT_LIST ) { - return decodeEntities( item.title?.rendered ) || __( '(no title)' ); - } - const linkProps = { - params: { - postId: item.id, - postType: item.type, - canvas: 'edit', - }, - }; - return ( - - { decodeEntities( item.title?.rendered ) || __( '(no title)' ) } - - ); -} - -function AuthorField( { item } ) { - const [ isImageLoaded, setIsImageLoaded ] = useState( false ); - const { text, icon, imageUrl } = useAddedBy( item.type, item.id ); - - return ( - - { imageUrl && ( -
- setIsImageLoaded( true ) } - alt="" - src={ imageUrl } - /> -
- ) } - { ! imageUrl && ( -
- -
- ) } - { text } -
- ); -} - -function Preview( { item, viewType } ) { - const settings = usePatternSettings(); - const [ backgroundColor = 'white' ] = useGlobalStyle( 'color.background' ); - const blocks = useMemo( () => { - return parse( item.content.raw ); - }, [ item.content.raw ] ); - const { onClick } = useLink( { - postId: item.id, - postType: item.type, - canvas: 'edit', - } ); - - const isEmpty = ! blocks?.length; - // Wrap everything in a block editor provider to ensure 'styles' that are needed - // for the previews are synced between the site editor store and the block editor store. - // Additionally we need to have the `__experimentalBlockPatterns` setting in order to - // render patterns inside the previews. - // TODO: Same approach is used in the patterns list and it becomes obvious that some of - // the block editor settings are needed in context where we don't have the block editor. - // Explore how we can solve this in a better way. - return ( - -
- { viewType === LAYOUT_LIST && ! isEmpty && ( - - - - ) } - { viewType !== LAYOUT_LIST && ( - - ) } -
-
- ); -} - export default function PageTemplates() { const { params } = useLocation(); const { activeView = 'all', layout, postId } = params; @@ -285,50 +171,15 @@ export default function PageTemplates() { const fields = useMemo( () => [ + previewField, + titleField, + descriptionField, { - label: __( 'Preview' ), - id: 'preview', - render: ( { item } ) => { - return ; - }, - enableSorting: false, - }, - { - label: __( 'Template' ), - id: 'title', - getValue: ( { item } ) => item.title?.rendered, - render: ( { item } ) => ( - - ), - enableHiding: false, - enableGlobalSearch: true, - }, - { - label: __( 'Description' ), - id: 'description', - render: ( { item } ) => { - return ( - item.description && ( - <span className="page-templates-description"> - { decodeEntities( item.description ) } - </span> - ) - ); - }, - enableSorting: false, - enableGlobalSearch: true, - }, - { - label: __( 'Author' ), - id: 'author', - getValue: ( { item } ) => item.author_text, - render: ( { item } ) => { - return <AuthorField viewType={ view.type } item={ item } />; - }, + ...authorField, elements: authors, }, ], - [ authors, view.type ] + [ authors ] ); const { data, paginationInfo } = useMemo( () => { diff --git a/packages/edit-site/src/components/page-templates/style.scss b/packages/edit-site/src/components/page-templates/style.scss index 725a3c4f89746c..f3d596cc90f18e 100644 --- a/packages/edit-site/src/components/page-templates/style.scss +++ b/packages/edit-site/src/components/page-templates/style.scss @@ -22,19 +22,19 @@ } } - &.is-viewtype-list { + .dataviews-view-list & { .block-editor-block-preview__container { height: 120px; } } - &.is-viewtype-grid { + .dataviews-view-grid & { .block-editor-block-preview__container { height: 100%; } } - &.is-viewtype-table { + .dataviews-view-table & { border-radius: $radius-block-ui; position: relative; From c460ed3a799302ce85dd4b8b17c1d8becb7a6a9d Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 25 Jul 2024 17:33:24 +0200 Subject: [PATCH 08/26] DataViews: Optimize the patterns dataviews by extracting the fields definition (#63927) Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: ellatrix <ellatrix@git.wordpress.org> Co-authored-by: jameskoster <jameskoster@git.wordpress.org> --- .../src/components/page-patterns/fields.js | 251 +++++++++++++++++ .../src/components/page-patterns/index.js | 258 +----------------- .../src/components/page-patterns/style.scss | 166 ++++++----- 3 files changed, 346 insertions(+), 329 deletions(-) create mode 100644 packages/edit-site/src/components/page-patterns/fields.js diff --git a/packages/edit-site/src/components/page-patterns/fields.js b/packages/edit-site/src/components/page-patterns/fields.js new file mode 100644 index 00000000000000..eab6dbca32833a --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/fields.js @@ -0,0 +1,251 @@ +/** + * External dependencies + */ +import clsx from 'clsx'; + +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + Button, + Tooltip, + Flex, +} from '@wordpress/components'; +import { __, _x } from '@wordpress/i18n'; +import { useState, useMemo, useId } from '@wordpress/element'; +import { + BlockPreview, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { Icon, lockSmall } from '@wordpress/icons'; +import { parse } from '@wordpress/blocks'; +import { decodeEntities } from '@wordpress/html-entities'; + +/** + * Internal dependencies + */ +import { Async } from '../async'; +import { + PATTERN_TYPES, + TEMPLATE_PART_POST_TYPE, + PATTERN_SYNC_TYPES, + OPERATOR_IS, +} from '../../utils/constants'; +import { unlock } from '../../lock-unlock'; +import { useLink } from '../routes/link'; +import { useAddedBy } from '../page-templates/hooks'; +import { defaultGetTitle } from './search-items'; + +const { useGlobalStyle } = unlock( blockEditorPrivateApis ); + +function PreviewWrapper( { item, onClick, ariaDescribedBy, children } ) { + return ( + <button + className="page-patterns-preview-field__button" + type="button" + onClick={ item.type !== PATTERN_TYPES.theme ? onClick : undefined } + aria-label={ item.title } + aria-describedby={ ariaDescribedBy } + aria-disabled={ item.type === PATTERN_TYPES.theme } + > + { children } + </button> + ); +} + +function PreviewField( { item } ) { + const descriptionId = useId(); + const description = item.description || item?.excerpt?.raw; + const isUserPattern = item.type === PATTERN_TYPES.user; + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; + const [ backgroundColor ] = useGlobalStyle( 'color.background' ); + const { onClick } = useLink( { + postType: item.type, + postId: isUserPattern || isTemplatePart ? item.id : item.name, + canvas: 'edit', + } ); + const blocks = useMemo( () => { + return ( + item.blocks ?? + parse( item.content.raw, { + __unstableSkipMigrationLogs: true, + } ) + ); + }, [ item?.content?.raw, item.blocks ] ); + const isEmpty = ! blocks?.length; + + return ( + <div + className="page-patterns-preview-field" + style={ { backgroundColor } } + > + <PreviewWrapper + item={ item } + onClick={ onClick } + ariaDescribedBy={ !! description ? descriptionId : undefined } + > + { isEmpty && isTemplatePart && __( 'Empty template part' ) } + { isEmpty && ! isTemplatePart && __( 'Empty pattern' ) } + { ! isEmpty && ( + <Async> + <BlockPreview + blocks={ blocks } + viewportWidth={ item.viewportWidth } + /> + </Async> + ) } + </PreviewWrapper> + { !! description && ( + <div hidden id={ descriptionId }> + { description } + </div> + ) } + </div> + ); +} + +export const previewField = { + label: __( 'Preview' ), + id: 'preview', + render: PreviewField, + enableSorting: false, +}; + +function TitleField( { item } ) { + const isUserPattern = item.type === PATTERN_TYPES.user; + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; + const { onClick } = useLink( { + postType: item.type, + postId: isUserPattern || isTemplatePart ? item.id : item.name, + canvas: 'edit', + } ); + const title = decodeEntities( defaultGetTitle( item ) ); + return ( + <HStack alignment="center" justify="flex-start" spacing={ 2 }> + <Flex + as="div" + gap={ 0 } + justify="left" + className="edit-site-patterns__pattern-title" + > + { item.type === PATTERN_TYPES.theme ? ( + title + ) : ( + <Button + variant="link" + onClick={ onClick } + // Required for the grid's roving tab index system. + // See https://github.com/WordPress/gutenberg/pull/51898#discussion_r1243399243. + tabIndex="-1" + > + { title } + </Button> + ) } + </Flex> + { item.type === PATTERN_TYPES.theme && ( + <Tooltip + placement="top" + text={ __( 'This pattern cannot be edited.' ) } + > + <Icon + className="edit-site-patterns__pattern-lock-icon" + icon={ lockSmall } + size={ 24 } + /> + </Tooltip> + ) } + </HStack> + ); +} + +export const titleField = { + label: __( 'Title' ), + id: 'title', + getValue: ( { item } ) => item.title?.raw || item.title, + render: TitleField, + enableHiding: false, +}; + +const SYNC_FILTERS = [ + { + value: PATTERN_SYNC_TYPES.full, + label: _x( 'Synced', 'pattern (singular)' ), + description: __( 'Patterns that are kept in sync across the site.' ), + }, + { + value: PATTERN_SYNC_TYPES.unsynced, + label: _x( 'Not synced', 'pattern (singular)' ), + description: __( + 'Patterns that can be changed freely without affecting the site.' + ), + }, +]; + +export const patternStatusField = { + label: __( 'Sync status' ), + id: 'sync-status', + render: ( { item } ) => { + const syncStatus = + 'wp_pattern_sync_status' in item + ? item.wp_pattern_sync_status || PATTERN_SYNC_TYPES.full + : PATTERN_SYNC_TYPES.unsynced; + // User patterns can have their sync statuses checked directly. + // Non-user patterns are all unsynced for the time being. + return ( + <span + className={ `edit-site-patterns__field-sync-status-${ syncStatus }` } + > + { + SYNC_FILTERS.find( ( { value } ) => value === syncStatus ) + .label + } + </span> + ); + }, + elements: SYNC_FILTERS, + filterBy: { + operators: [ OPERATOR_IS ], + isPrimary: true, + }, + enableSorting: false, +}; + +function AuthorField( { item } ) { + const [ isImageLoaded, setIsImageLoaded ] = useState( false ); + const { text, icon, imageUrl } = useAddedBy( item.type, item.id ); + + return ( + <HStack alignment="left" spacing={ 0 }> + { imageUrl && ( + <div + className={ clsx( 'page-templates-author-field__avatar', { + 'is-loaded': isImageLoaded, + } ) } + > + <img + onLoad={ () => setIsImageLoaded( true ) } + alt="" + src={ imageUrl } + /> + </div> + ) } + { ! imageUrl && ( + <div className="page-templates-author-field__icon"> + <Icon icon={ icon } /> + </div> + ) } + <span className="page-templates-author-field__name">{ text }</span> + </HStack> + ); +} + +export const templatePartAuthorField = { + label: __( 'Author' ), + id: 'author', + getValue: ( { item } ) => item.author_text, + render: AuthorField, + filterBy: { + isPrimary: true, + }, +}; diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index a0f107e91601bd..a8db73272c0ce7 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -1,59 +1,39 @@ -/** - * External dependencies - */ -import clsx from 'clsx'; - /** * WordPress dependencies */ -import { - __experimentalHStack as HStack, - Button, - Tooltip, - Flex, -} from '@wordpress/components'; -import { __, _x } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { useState, useMemo, useId, useEffect } from '@wordpress/element'; -import { - BlockPreview, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; -import { Icon, lockSmall } from '@wordpress/icons'; import { usePrevious } from '@wordpress/compose'; import { useEntityRecords } from '@wordpress/core-data'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { parse } from '@wordpress/blocks'; -import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies */ -import { Async } from '../async'; import Page from '../page'; import { LAYOUT_GRID, LAYOUT_TABLE, - LAYOUT_LIST, PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, - PATTERN_SYNC_TYPES, PATTERN_DEFAULT_CATEGORY, - OPERATOR_IS, } from '../../utils/constants'; import usePatternSettings from './use-pattern-settings'; import { unlock } from '../../lock-unlock'; import usePatterns from './use-patterns'; import PatternsHeader from './header'; -import { useLink } from '../routes/link'; -import { useAddedBy } from '../page-templates/hooks'; import { useEditPostAction } from '../dataviews-actions'; -import { defaultGetTitle } from './search-items'; +import { + patternStatusField, + previewField, + titleField, + templatePartAuthorField, +} from './fields'; -const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock( - blockEditorPrivateApis -); +const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { usePostActions } = unlock( editorPrivateApis ); const { useLocation } = unlock( routerPrivateApis ); @@ -90,164 +70,6 @@ const DEFAULT_VIEW = { filters: [], }; -const SYNC_FILTERS = [ - { - value: PATTERN_SYNC_TYPES.full, - label: _x( 'Synced', 'pattern (singular)' ), - description: __( 'Patterns that are kept in sync across the site.' ), - }, - { - value: PATTERN_SYNC_TYPES.unsynced, - label: _x( 'Not synced', 'pattern (singular)' ), - description: __( - 'Patterns that can be changed freely without affecting the site.' - ), - }, -]; - -function PreviewWrapper( { item, onClick, ariaDescribedBy, children } ) { - return ( - <button - className="page-patterns-preview-field__button" - type="button" - onClick={ item.type !== PATTERN_TYPES.theme ? onClick : undefined } - aria-label={ item.title } - aria-describedby={ ariaDescribedBy } - aria-disabled={ item.type === PATTERN_TYPES.theme } - > - { children } - </button> - ); -} - -function Preview( { item, viewType } ) { - const descriptionId = useId(); - const description = item.description || item?.excerpt?.raw; - const isUserPattern = item.type === PATTERN_TYPES.user; - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const [ backgroundColor ] = useGlobalStyle( 'color.background' ); - const { onClick } = useLink( { - postType: item.type, - postId: isUserPattern || isTemplatePart ? item.id : item.name, - canvas: 'edit', - } ); - const blocks = useMemo( () => { - return ( - item.blocks ?? - parse( item.content.raw, { - __unstableSkipMigrationLogs: true, - } ) - ); - }, [ item?.content?.raw, item.blocks ] ); - const isEmpty = ! blocks?.length; - - return ( - <div - className={ `page-patterns-preview-field is-viewtype-${ viewType }` } - style={ { backgroundColor } } - > - <PreviewWrapper - item={ item } - onClick={ onClick } - ariaDescribedBy={ !! description ? descriptionId : undefined } - > - { isEmpty && isTemplatePart && __( 'Empty template part' ) } - { isEmpty && ! isTemplatePart && __( 'Empty pattern' ) } - { ! isEmpty && ( - <Async> - <BlockPreview - blocks={ blocks } - viewportWidth={ item.viewportWidth } - /> - </Async> - ) } - </PreviewWrapper> - { !! description && ( - <div hidden id={ descriptionId }> - { description } - </div> - ) } - </div> - ); -} - -function Author( { item, viewType } ) { - const [ isImageLoaded, setIsImageLoaded ] = useState( false ); - const { text, icon, imageUrl } = useAddedBy( item.type, item.id ); - const withIcon = viewType !== LAYOUT_LIST; - - return ( - <HStack alignment="left" spacing={ 0 }> - { withIcon && imageUrl && ( - <div - className={ clsx( 'page-templates-author-field__avatar', { - 'is-loaded': isImageLoaded, - } ) } - > - <img - onLoad={ () => setIsImageLoaded( true ) } - alt="" - src={ imageUrl } - /> - </div> - ) } - { withIcon && ! imageUrl && ( - <div className="page-templates-author-field__icon"> - <Icon icon={ icon } /> - </div> - ) } - <span className="page-templates-author-field__name">{ text }</span> - </HStack> - ); -} - -function Title( { item } ) { - const isUserPattern = item.type === PATTERN_TYPES.user; - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const { onClick } = useLink( { - postType: item.type, - postId: isUserPattern || isTemplatePart ? item.id : item.name, - canvas: 'edit', - } ); - const title = decodeEntities( defaultGetTitle( item ) ); - return ( - <HStack alignment="center" justify="flex-start" spacing={ 2 }> - <Flex - as="div" - gap={ 0 } - justify="left" - className="edit-site-patterns__pattern-title" - > - { item.type === PATTERN_TYPES.theme ? ( - title - ) : ( - <Button - variant="link" - onClick={ onClick } - // Required for the grid's roving tab index system. - // See https://github.com/WordPress/gutenberg/pull/51898#discussion_r1243399243. - tabIndex="-1" - > - { title } - </Button> - ) } - </Flex> - { item.type === PATTERN_TYPES.theme && ( - <Tooltip - placement="top" - text={ __( 'This pattern cannot be edited.' ) } - > - <Icon - className="edit-site-patterns__pattern-lock-icon" - icon={ lockSmall } - size={ 24 } - /> - </Tooltip> - ) } - </HStack> - ); -} - export default function DataviewsPatterns() { const { params: { postType, categoryId: categoryIdFromURL }, @@ -267,6 +89,7 @@ export default function DataviewsPatterns() { const { records } = useEntityRecords( 'postType', TEMPLATE_PART_POST_TYPE, { per_page: -1, } ); + const authors = useMemo( () => { if ( ! records ) { return EMPTY_ARRAY; @@ -282,72 +105,19 @@ export default function DataviewsPatterns() { }, [ records ] ); const fields = useMemo( () => { - const _fields = [ - { - label: __( 'Preview' ), - id: 'preview', - render: ( { item } ) => ( - <Preview item={ item } viewType={ view.type } /> - ), - enableSorting: false, - }, - { - label: __( 'Title' ), - id: 'title', - getValue: ( { item } ) => item.title?.raw || item.title, - render: ( { item } ) => <Title item={ item } />, - enableHiding: false, - }, - ]; + const _fields = [ previewField, titleField ]; if ( type === PATTERN_TYPES.user ) { - _fields.push( { - label: __( 'Sync status' ), - id: 'sync-status', - render: ( { item } ) => { - const syncStatus = - 'wp_pattern_sync_status' in item - ? item.wp_pattern_sync_status || - PATTERN_SYNC_TYPES.full - : PATTERN_SYNC_TYPES.unsynced; - // User patterns can have their sync statuses checked directly. - // Non-user patterns are all unsynced for the time being. - return ( - <span - className={ `edit-site-patterns__field-sync-status-${ syncStatus }` } - > - { - SYNC_FILTERS.find( - ( { value } ) => value === syncStatus - ).label - } - </span> - ); - }, - elements: SYNC_FILTERS, - filterBy: { - operators: [ OPERATOR_IS ], - isPrimary: true, - }, - enableSorting: false, - } ); + _fields.push( patternStatusField ); } else if ( type === TEMPLATE_PART_POST_TYPE ) { _fields.push( { - label: __( 'Author' ), - id: 'author', - getValue: ( { item } ) => item.author_text, - render: ( { item } ) => { - return <Author viewType={ view.type } item={ item } />; - }, + ...templatePartAuthorField, elements: authors, - filterBy: { - isPrimary: true, - }, } ); } return _fields; - }, [ view.type, type, authors ] ); + }, [ type, authors ] ); // Reset the page number when the category changes. useEffect( () => { diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index 2014d3b94fdae3..f457624d100c1f 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -1,118 +1,114 @@ -.edit-site-patterns__section-header { - .screen-reader-shortcut:focus { - top: 0; - } +.edit-site-patterns__delete-modal { + width: $modal-width-small; } -.edit-site-patterns__pattern-title { - .is-link { - text-decoration: none; - color: $gray-200; +.page-patterns-preview-field { + display: flex; + flex-direction: column; + height: 100%; - &:hover, - &:focus { - color: $white; + .dataviews-view-table & { + width: 96px; + flex-grow: 0; + border-radius: 2px; + + .page-patterns-preview-field__button { + border-radius: 2px; } } - .edit-site-patterns__pattern-icon { + .page-patterns-preview-field__button { + box-shadow: none; + border: none; + padding: 0; + background-color: unset; + box-sizing: border-box; + cursor: pointer; + overflow: hidden; + height: 100%; border-radius: $grid-unit-05; - background: var(--wp-block-synced-color); - fill: $white; - } - .edit-site-patterns__pattern-lock-icon { - fill: currentcolor; + &:focus-visible { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + + &[aria-disabled="true"] { + cursor: default; + } } } -.edit-site-patterns__delete-modal { - width: $modal-width-small; +.edit-site-patterns__pattern-icon { + fill: var(--wp-block-synced-color); + flex-shrink: 0; } -.edit-site-page-patterns-dataviews { - .page-patterns-preview-field { - display: flex; - flex-direction: column; - height: 100%; - - &.is-viewtype-table { - width: 96px; - flex-grow: 0; - border-radius: 2px; +.edit-site-patterns__pattern-lock-icon { + min-width: min-content; +} - .page-patterns-preview-field__button { - border-radius: 2px; - } - } +.edit-site-patterns__section-header { + border-bottom: 1px solid #f0f0f0; + padding: $grid-unit-20 $grid-unit-60; + position: sticky; + top: 0; + z-index: 2; + flex-shrink: 0; + transition: padding ease-out 0.1s; + @include reduce-motion("transition"); + min-height: $grid-unit-50; + + .edit-site-patterns__title { + min-height: $grid-unit-50; - .page-patterns-preview-field__button { - box-shadow: none; - border: none; - padding: 0; - background-color: unset; - box-sizing: border-box; - cursor: pointer; - overflow: hidden; - height: 100%; - border-radius: $grid-unit-05; - - &:focus-visible { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } - - &[aria-disabled="true"] { - cursor: default; - } + .components-heading { + flex-grow: 1; + flex-basis: 0; + white-space: nowrap; } } - .edit-site-patterns__pattern-icon { - fill: var(--wp-block-synced-color); - flex-shrink: 0; + .edit-site-patterns__sub-title { + margin-bottom: $grid-unit-10; } - .edit-site-patterns__pattern-lock-icon { - min-width: min-content; + .screen-reader-shortcut:focus { + top: 0; } +} - .edit-site-patterns__section-header { - border-bottom: 1px solid #f0f0f0; - padding: $grid-unit-20 $grid-unit-60; - position: sticky; - top: 0; - z-index: 2; - flex-shrink: 0; - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); - min-height: $grid-unit-50; +.edit-site-patterns__pattern-title { + display: block; + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: inherit; - .edit-site-patterns__title { - min-height: $grid-unit-50; + .is-link { + text-decoration: none; + color: $gray-200; - .components-heading { - flex-grow: 1; - flex-basis: 0; - white-space: nowrap; - } + &:hover, + &:focus { + color: $white; } + } - .edit-site-patterns__sub-title { - margin-bottom: $grid-unit-10; - } + .edit-site-patterns__pattern-icon { + border-radius: $grid-unit-05; + background: var(--wp-block-synced-color); + fill: $white; } - .edit-site-patterns__pattern-title { - display: block; - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: inherit; + .edit-site-patterns__pattern-lock-icon { + fill: currentcolor; } +} +.edit-site-page-patterns-dataviews { .dataviews-pagination { z-index: z-index(".edit-site-patterns__dataviews-list-pagination"); } From 2fcba1b3d8f91ba067306f1701bf3968bf2df436 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:22:26 +0200 Subject: [PATCH 09/26] Move logic outside reducer (#63941) Move bindings logic to merge usesContext outside reducer Co-authored-by: SantosGuillamot <santosguillamot@git.wordpress.org> Co-authored-by: gziolo <gziolo@git.wordpress.org> --- packages/blocks/src/store/reducer.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 2f141fb0cf9927..9fd97f0937b332 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -371,16 +371,21 @@ export function collections( state = {}, action ) { return state; } -export function blockBindingsSources( state = {}, action ) { - // Merge usesContext with existing values, potentially defined in the server registration. - const existingUsesContext = state[ action.name ]?.usesContext || []; - const newUsesContext = action.usesContext || []; +/** + * Merges usesContext with existing values, potentially defined in the server registration. + * + * @param {string[]} existingUsesContext Existing `usesContext`. + * @param {string[]} newUsesContext Newly added `usesContext`. + * @return {string[]|undefined} Merged `usesContext`. + */ +function getMergedUsesContext( existingUsesContext = [], newUsesContext = [] ) { const mergedArrays = Array.from( new Set( existingUsesContext.concat( newUsesContext ) ) ); - const mergedUsesContext = - mergedArrays.length > 0 ? mergedArrays : undefined; + return mergedArrays.length > 0 ? mergedArrays : undefined; +} +export function blockBindingsSources( state = {}, action ) { switch ( action.type ) { case 'ADD_BLOCK_BINDINGS_SOURCE': return { @@ -388,7 +393,10 @@ export function blockBindingsSources( state = {}, action ) { [ action.name ]: { // Don't override the label if it's already set. label: state[ action.name ]?.label || action.label, - usesContext: mergedUsesContext, + usesContext: getMergedUsesContext( + state[ action.name ]?.usesContext, + action.usesContext + ), getValues: action.getValues, setValues: action.setValues, getPlaceholder: action.getPlaceholder, @@ -405,7 +413,10 @@ export function blockBindingsSources( state = {}, action ) { */ ...state[ action.name ], label: action.label, - usesContext: mergedUsesContext, + usesContext: getMergedUsesContext( + state[ action.name ]?.usesContext, + action.usesContext + ), }, }; case 'REMOVE_BLOCK_BINDINGS_SOURCE': From cad81e428aed155fa3e490cc9c8d9673a170c3fd Mon Sep 17 00:00:00 2001 From: Shail Mehta <shailmehta25@gmail.com> Date: Thu, 25 Jul 2024 23:44:43 +0530 Subject: [PATCH 10/26] Small Typo Correction in Inline Document (#63952) Co-authored-by: shail-mehta <shailu25@git.wordpress.org> Co-authored-by: SantosGuillamot <santosguillamot@git.wordpress.org> --- lib/compat/wordpress-6.7/block-bindings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php index 4c82dc6683f370..398b53b340673b 100644 --- a/lib/compat/wordpress-6.7/block-bindings.php +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -1,6 +1,6 @@ <?php /** - * Temporary compatibility code for new functionalitites/changes related to block bindings APIs present in Gutenberg. + * Temporary compatibility code for new functionalities/changes related to block bindings APIs present in Gutenberg. * * @package gutenberg */ From d1a2f43d2cfe4039d6a72b3e0160cda2a0323881 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:02:58 +1000 Subject: [PATCH 11/26] Tag Cloud: Prevent duplicate spacing in editor (#63832) Co-authored-by: aaronrobertshaw <aaronrobertshaw@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: Mamaduka <mamaduka@git.wordpress.org> Co-authored-by: akasunil <sunil25393@git.wordpress.org> Co-authored-by: richtabor <richtabor@git.wordpress.org> --- packages/block-library/src/editor.scss | 1 + packages/block-library/src/tag-cloud/editor.scss | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 packages/block-library/src/tag-cloud/editor.scss diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index c43137c632b73f..52f3aa64287fae 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -44,6 +44,7 @@ @import "./social-links/editor.scss"; @import "./spacer/editor.scss"; @import "./table/editor.scss"; +@import "./tag-cloud/editor.scss"; @import "./template-part/editor.scss"; @import "./text-columns/editor.scss"; @import "./video/editor.scss"; diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss new file mode 100644 index 00000000000000..de2a95a386fa85 --- /dev/null +++ b/packages/block-library/src/tag-cloud/editor.scss @@ -0,0 +1,9 @@ +// The following styles are to prevent duplicate spacing for the tag cloud +// block in the editor given it uses server side rendering. The specificity +// must be higher than `0-1-0` to override global styles. Targeting the +// inner use of the .wp-block-tag-cloud class should minimize impact on +// other 3rd party styles targeting the block. +.wp-block-tag-cloud .wp-block-tag-cloud { + margin: 0; + padding: 0; +} From e98f358d82ef908f9d26be87c330a380ef799516 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:58:30 +1000 Subject: [PATCH 12/26] Grid layout: Try moving resizer popover slot to fix an issue on mobile (#63920) Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: noisysocks <noisysocks@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> --- packages/block-editor/src/components/grid/grid-item-resizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/grid/grid-item-resizer.js b/packages/block-editor/src/components/grid/grid-item-resizer.js index 6f6fa655b35568..34bc1db6048067 100644 --- a/packages/block-editor/src/components/grid/grid-item-resizer.js +++ b/packages/block-editor/src/components/grid/grid-item-resizer.js @@ -98,7 +98,7 @@ function GridItemResizerInner( { <BlockPopoverCover className="block-editor-grid-item-resizer" clientId={ clientId } - __unstablePopoverSlot="block-toolbar" + __unstablePopoverSlot="__unstable-block-tools-after" additionalStyles={ styles } > <ResizableBox From eea7183c6b156610cd3daa538e98a8b4b5388c2e Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 26 Jul 2024 08:44:03 +0200 Subject: [PATCH 13/26] Fix unlabeled Spacer block controls (#63806) * Fix missing label for horizontal spacer. * Remove unnecessary BaseControl. * Add missing label to SpacingInputControl RangeControl. * Hide label from vision. * Use 40px size for the Unit control. * Try grid layout for the single unit control. * Remove Grid experiment and show single unit control full width. * Restore conditional that was changed for testing purposes. Co-authored-by: afercia <afercia@git.wordpress.org> Co-authored-by: tyxla <tyxla@git.wordpress.org> Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: joedolson <joedolson@git.wordpress.org> --- .../components/child-layout-control/index.js | 2 ++ .../input-controls/spacing-input-control.js | 2 ++ packages/block-library/src/spacer/controls.js | 23 ++++++++----------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/child-layout-control/index.js b/packages/block-editor/src/components/child-layout-control/index.js index 698ea2d2d74a42..022acf2e1074a4 100644 --- a/packages/block-editor/src/components/child-layout-control/index.js +++ b/packages/block-editor/src/components/child-layout-control/index.js @@ -174,6 +174,8 @@ function FlexControls( { } ); } } value={ flexSize } + label={ flexResetLabel } + hideLabelFromVision /> ) } </VStack> diff --git a/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js b/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js index bec589f2ed9aa8..6605d2de502fe8 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js +++ b/packages/block-editor/src/components/spacing-sizes-control/input-controls/spacing-input-control.js @@ -266,6 +266,8 @@ export default function SpacingInputControl( { onChange={ handleCustomValueSliderChange } className="spacing-sizes-control__custom-value-range" __nextHasNoMarginBottom + label={ ariaLabel } + hideLabelFromVision /> </> ) } diff --git a/packages/block-library/src/spacer/controls.js b/packages/block-library/src/spacer/controls.js index 160335fcc092e9..1e899e15aff0de 100644 --- a/packages/block-library/src/spacer/controls.js +++ b/packages/block-library/src/spacer/controls.js @@ -10,7 +10,6 @@ import { privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { - BaseControl, PanelBody, __experimentalUseCustomUnits as useCustomUnits, __experimentalUnitControl as UnitControl, @@ -57,19 +56,17 @@ function DimensionInput( { label, onChange, isResizing, value = '' } ) { return ( <> { ( ! spacingSizes || spacingSizes?.length === 0 ) && ( - <BaseControl label={ label } id={ inputId }> - <UnitControl - id={ inputId } - isResetValueOnUnitChange - min={ MIN_SPACER_SIZE } - onChange={ handleOnChange } - style={ { maxWidth: 80 } } - value={ computedValue } - units={ units } - /> - </BaseControl> + <UnitControl + id={ inputId } + isResetValueOnUnitChange + min={ MIN_SPACER_SIZE } + onChange={ handleOnChange } + value={ computedValue } + units={ units } + label={ label } + __next40pxDefaultSize + /> ) } - { spacingSizes?.length > 0 && ( <View className="tools-panel-item-spacing"> <SpacingSizesControl From 9f7e26ff4be7e23baeb7afb56bc98a221efcf013 Mon Sep 17 00:00:00 2001 From: George Mamadashvili <georgemamadashvili@gmail.com> Date: Fri, 26 Jul 2024 11:36:21 +0400 Subject: [PATCH 14/26] TemplateContentPanel: Fix the 'getBlocksByName' selector call (#63922) Co-authored-by: Mamaduka <mamaduka@git.wordpress.org> Co-authored-by: ellatrix <ellatrix@git.wordpress.org> Co-authored-by: noisysocks <noisysocks@git.wordpress.org> --- .../components/provider/disable-non-page-content-blocks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/provider/disable-non-page-content-blocks.js b/packages/editor/src/components/provider/disable-non-page-content-blocks.js index 35b060b096c069..9abb0e14079d5e 100644 --- a/packages/editor/src/components/provider/disable-non-page-content-blocks.js +++ b/packages/editor/src/components/provider/disable-non-page-content-blocks.js @@ -45,8 +45,8 @@ export default function DisableNonPageContentBlocks() { ); const disabledIds = useSelect( ( select ) => { const { getBlocksByName, getBlockOrder } = select( blockEditorStore ); - return getBlocksByName( [ 'core/template-part' ] ).flatMap( - ( clientId ) => getBlockOrder( clientId ) + return getBlocksByName( 'core/template-part' ).flatMap( ( clientId ) => + getBlockOrder( clientId ) ); }, [] ); From 654fef6f20cdb004b13efe5064175c297bf96150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:58:38 +0200 Subject: [PATCH 15/26] DataForm: migrate order action modal and introduce form validation (#63895) Co-authored-by: oandregal <oandregal@git.wordpress.org> Co-authored-by: youknowriad <youknowriad@git.wordpress.org> --- .../src/components/dataform/index.tsx | 38 ++++++++++- .../dataform/stories/index.story.tsx | 8 ++- packages/dataviews/src/index.ts | 1 + packages/dataviews/src/test/validation.ts | 63 +++++++++++++++++++ packages/dataviews/src/types.ts | 7 ++- packages/dataviews/src/validation.ts | 33 ++++++++++ .../src/components/post-actions/actions.js | 49 ++++++++------- 7 files changed, 172 insertions(+), 27 deletions(-) create mode 100644 packages/dataviews/src/test/validation.ts create mode 100644 packages/dataviews/src/validation.ts diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index 64196c685a9784..42a6766813975e 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -6,13 +6,16 @@ import type { Dispatch, SetStateAction } from 'react'; /** * WordPress dependencies */ -import { TextControl } from '@wordpress/components'; +import { + TextControl, + __experimentalNumberControl as NumberControl, +} from '@wordpress/components'; import { useCallback, useMemo } from '@wordpress/element'; /** * Internal dependencies */ -import type { Form, Field, NormalizedField } from '../../types'; +import type { Form, Field, NormalizedField, FieldType } from '../../types'; import { normalizeFields } from '../../normalize-fields'; type DataFormProps< Item > = { @@ -56,12 +59,41 @@ function DataFormTextControl< Item >( { ); } +function DataFormNumberControl< Item >( { + data, + field, + onChange, +}: DataFormControlProps< Item > ) { + const { id, label, description } = field; + const value = field.getValue( { item: data } ); + + const onChangeControl = useCallback( + ( newValue: string | undefined ) => + onChange( ( prevItem: Item ) => ( { + ...prevItem, + [ id ]: newValue, + } ) ), + [ id, onChange ] + ); + + return ( + <NumberControl + label={ label } + help={ description } + value={ value } + onChange={ onChangeControl } + __next40pxDefaultSize + /> + ); +} + const controls: { - [ key: string ]: < Item >( + [ key in FieldType ]: < Item >( props: DataFormControlProps< Item > ) => JSX.Element; } = { text: DataFormTextControl, + integer: DataFormNumberControl, }; function getControlForField< Item >( field: NormalizedField< Item > ) { diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx index 2e288c8e11d41c..a67eaa6b76f042 100644 --- a/packages/dataviews/src/components/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx @@ -20,15 +20,21 @@ const fields = [ label: 'Title', type: 'text' as const, }, + { + id: 'order', + label: 'Order', + type: 'integer' as const, + }, ]; export const Default = () => { const [ post, setPost ] = useState( { title: 'Hello, World!', + order: 2, } ); const form = { - visibleFields: [ 'title' ], + visibleFields: [ 'title', 'order' ], }; return ( diff --git a/packages/dataviews/src/index.ts b/packages/dataviews/src/index.ts index 8b6e53e1ff7293..95a8ab4c2e5e8d 100644 --- a/packages/dataviews/src/index.ts +++ b/packages/dataviews/src/index.ts @@ -3,3 +3,4 @@ export { default as DataForm } from './components/dataform'; export { VIEW_LAYOUTS } from './layouts'; export { filterSortAndPaginate } from './filter-and-sort-data-view'; export type * from './types'; +export { isItemValid } from './validation'; diff --git a/packages/dataviews/src/test/validation.ts b/packages/dataviews/src/test/validation.ts new file mode 100644 index 00000000000000..d90d4744ac3272 --- /dev/null +++ b/packages/dataviews/src/test/validation.ts @@ -0,0 +1,63 @@ +/** + * Internal dependencies + */ +import { isItemValid } from '../validation'; +import type { Field } from '../types'; + +describe( 'validation', () => { + it( 'fields not visible in form are not validated', () => { + const item = { id: 1, valid_order: 2, invalid_order: 'd' }; + const fields: Field< {} >[] = [ + { + id: 'valid_order', + type: 'integer', + }, + { + id: 'invalid_order', + type: 'integer', + }, + ]; + const form = { visibleFields: [ 'valid_order' ] }; + const result = isItemValid( item, fields, form ); + expect( result ).toBe( true ); + } ); + + it( 'integer field is valid if value is integer', () => { + const item = { id: 1, order: 2, title: 'hi' }; + const fields: Field< {} >[] = [ + { + type: 'integer', + id: 'order', + }, + ]; + const form = { visibleFields: [ 'order' ] }; + const result = isItemValid( item, fields, form ); + expect( result ).toBe( true ); + } ); + + it( 'integer field is invalid if value is not integer', () => { + const item = { id: 1, order: 'd' }; + const fields: Field< {} >[] = [ + { + id: 'order', + type: 'integer', + }, + ]; + const form = { visibleFields: [ 'order' ] }; + const result = isItemValid( item, fields, form ); + expect( result ).toBe( false ); + } ); + + it( 'integer field is invalid if value is empty', () => { + const item = { id: 1, order: '' }; + const fields: Field< {} >[] = [ + { + id: 'order', + type: 'integer', + }, + ]; + const form = { visibleFields: [ 'order' ] }; + const result = isItemValid( item, fields, form ); + expect( result ).toBe( false ); + } ); +} ); diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 0b43740efc3f2d..37c3efbde5cfb0 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -44,7 +44,7 @@ export type Operator = export type ItemRecord = Record< string, unknown >; -export type FieldType = 'text'; +export type FieldType = 'text' | 'integer'; /** * A dataview field for a specific property of a data type. @@ -65,6 +65,11 @@ export type Field< Item > = { */ label?: string; + /** + * A description of the field. + */ + description?: string; + /** * Placeholder for the field. */ diff --git a/packages/dataviews/src/validation.ts b/packages/dataviews/src/validation.ts new file mode 100644 index 00000000000000..5b20d094a41861 --- /dev/null +++ b/packages/dataviews/src/validation.ts @@ -0,0 +1,33 @@ +/** + * Internal dependencies + */ +import { normalizeFields } from './normalize-fields'; +import type { Field, Form } from './types'; + +export function isItemValid< Item >( + item: Item, + fields: Field< Item >[], + form: Form +): boolean { + const _fields = normalizeFields( + fields.filter( ( { id } ) => !! form.visibleFields?.includes( id ) ) + ); + return _fields.every( ( field ) => { + const value = field.getValue( { item } ); + + // TODO: this implicitely means the value is required. + if ( field.type === 'integer' && value === '' ) { + return false; + } + + if ( + field.type === 'integer' && + ! Number.isInteger( Number( value ) ) + ) { + return false; + } + + // Nothing to validate. + return true; + } ); +} diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 2a97e688eeedbe..190b8ea6ca32f5 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -11,14 +11,13 @@ import { store as noticesStore } from '@wordpress/notices'; import { useMemo, useState } from '@wordpress/element'; import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { parse } from '@wordpress/blocks'; -import { DataForm } from '@wordpress/dataviews'; +import { DataForm, isItemValid } from '@wordpress/dataviews'; import { Button, TextControl, __experimentalText as Text, __experimentalHStack as HStack, __experimentalVStack as VStack, - __experimentalNumberControl as NumberControl, } from '@wordpress/components'; /** @@ -39,21 +38,31 @@ import { getItemTitle } from '../../dataviews/actions/utils'; const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } = unlock( patternsPrivateApis ); -// TODO: this should be shared with other components (page-pages). +// TODO: this should be shared with other components (see post-fields in edit-site). const fields = [ { type: 'text', - header: __( 'Title' ), id: 'title', + label: __( 'Title' ), placeholder: __( 'No title' ), getValue: ( { item } ) => item.title, }, + { + type: 'integer', + id: 'menu_order', + label: __( 'Order' ), + description: __( 'Determines the order of pages.' ), + }, ]; -const form = { +const formDuplicateAction = { visibleFields: [ 'title' ], }; +const formOrderAction = { + visibleFields: [ 'menu_order' ], +}; + /** * Check if a template is removable. * @@ -635,21 +644,20 @@ function useRenamePostAction( postType ) { } function ReorderModal( { items, closeModal, onActionPerformed } ) { - const [ item ] = items; + const [ item, setItem ] = useState( items[ 0 ] ); + const orderInput = item.menu_order; const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); - const [ orderInput, setOrderInput ] = useState( item.menu_order ); async function onOrder( event ) { event.preventDefault(); - if ( - ! Number.isInteger( Number( orderInput ) ) || - orderInput?.trim?.() === '' - ) { + + if ( ! isItemValid( item, fields, formOrderAction ) ) { return; } + try { await editEntityRecord( 'postType', item.type, item.id, { menu_order: orderInput, @@ -673,9 +681,7 @@ function ReorderModal( { items, closeModal, onActionPerformed } ) { } ); } } - const saveIsDisabled = - ! Number.isInteger( Number( orderInput ) ) || - orderInput?.trim?.() === ''; + const isSaveDisabled = ! isItemValid( item, fields, formOrderAction ); return ( <form onSubmit={ onOrder }> <VStack spacing="5"> @@ -684,12 +690,11 @@ function ReorderModal( { items, closeModal, onActionPerformed } ) { 'Determines the order of pages. Pages with the same order value are sorted alphabetically. Negative order values are supported.' ) } </div> - <NumberControl - __next40pxDefaultSize - label={ __( 'Order' ) } - help={ __( 'Set the page order.' ) } - value={ orderInput } - onChange={ setOrderInput } + <DataForm + data={ item } + fields={ fields } + form={ formOrderAction } + onChange={ setItem } /> <HStack justify="right"> <Button @@ -706,7 +711,7 @@ function ReorderModal( { items, closeModal, onActionPerformed } ) { variant="primary" type="submit" accessibleWhenDisabled - disabled={ saveIsDisabled } + disabled={ isSaveDisabled } __experimentalIsFocusable > { __( 'Save' ) } @@ -873,7 +878,7 @@ const useDuplicatePostAction = ( postType ) => { <DataForm data={ item } fields={ fields } - form={ form } + form={ formDuplicateAction } onChange={ setItem } /> <HStack spacing={ 2 } justify="end"> From 1e28ca49aaedb395cd58abc2c11845ee97c05327 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 26 Jul 2024 10:58:53 +0200 Subject: [PATCH 16/26] Core Data: Add new useEntityRecordsWithPermissions hook (#63857) Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: Mamaduka <mamaduka@git.wordpress.org> Co-authored-by: ellatrix <ellatrix@git.wordpress.org> Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org> --- .../footnotes/get-rich-text-values-cached.js | 2 +- .../core-data/src/hooks/use-entity-records.ts | 50 +++++++++++++++++++ packages/core-data/src/index.js | 3 +- packages/core-data/src/lock-unlock.js | 10 ++++ packages/core-data/src/private-apis.js | 14 +++--- packages/core-data/src/private-selectors.ts | 23 +++++++++ .../src/components/post-list/index.js | 8 ++- 7 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 packages/core-data/src/lock-unlock.js diff --git a/packages/core-data/src/footnotes/get-rich-text-values-cached.js b/packages/core-data/src/footnotes/get-rich-text-values-cached.js index 06a01c5ef63fdd..a5c2d258108612 100644 --- a/packages/core-data/src/footnotes/get-rich-text-values-cached.js +++ b/packages/core-data/src/footnotes/get-rich-text-values-cached.js @@ -6,7 +6,7 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { unlock } from '../private-apis'; +import { unlock } from '../lock-unlock'; // TODO: The following line should have been: // diff --git a/packages/core-data/src/hooks/use-entity-records.ts b/packages/core-data/src/hooks/use-entity-records.ts index 5d643ab8896925..e2659b88bc0198 100644 --- a/packages/core-data/src/hooks/use-entity-records.ts +++ b/packages/core-data/src/hooks/use-entity-records.ts @@ -4,6 +4,7 @@ import { addQueryArgs } from '@wordpress/url'; import deprecated from '@wordpress/deprecated'; import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -12,6 +13,7 @@ import useQuerySelect from './use-query-select'; import { store as coreStore } from '../'; import type { Options } from './use-entity-record'; import type { Status } from './constants'; +import { unlock } from '../lock-unlock'; interface EntityRecordsResolution< RecordType > { /** The requested entity record */ @@ -152,3 +154,51 @@ export function __experimentalUseEntityRecords( } ); return useEntityRecords( kind, name, queryArgs, options ); } + +export function useEntityRecordsWithPermissions< RecordType >( + kind: string, + name: string, + queryArgs: Record< string, unknown > = {}, + options: Options = { enabled: true } +): EntityRecordsResolution< RecordType > { + const entityConfig = useSelect( + ( select ) => select( coreStore ).getEntityConfig( kind, name ), + [ kind, name ] + ); + const { records: data, ...ret } = useEntityRecords( + kind, + name, + queryArgs, + options + ); + const ids = useMemo( + () => + data?.map( + // @ts-ignore + ( record: RecordType ) => record[ entityConfig?.key ?? 'id' ] + ) ?? [], + [ data, entityConfig?.key ] + ); + + const permissions = useSelect( + ( select ) => { + const { getEntityRecordsPermissions } = unlock( + select( coreStore ) + ); + return getEntityRecordsPermissions( kind, name, ids ); + }, + [ ids, kind, name ] + ); + + const dataWithPermissions = useMemo( + () => + data?.map( ( record, index ) => ( { + // @ts-ignore + ...record, + permissions: permissions[ index ], + } ) ) ?? [], + [ data, permissions ] + ); + + return { records: dataWithPermissions, ...ret }; +} diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index bd25fa8de9902b..ad6adec0203c59 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -18,7 +18,7 @@ import { getMethodName, } from './entities'; import { STORE_NAME } from './name'; -import { unlock } from './private-apis'; +import { unlock } from './lock-unlock'; // The entity selectors/resolvers and actions are shortcuts to their generic equivalents // (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords) @@ -86,3 +86,4 @@ export * from './entity-provider'; export * from './entity-types'; export * from './fetch'; export * from './hooks'; +export * from './private-apis'; diff --git a/packages/core-data/src/lock-unlock.js b/packages/core-data/src/lock-unlock.js new file mode 100644 index 00000000000000..91bf30792c970a --- /dev/null +++ b/packages/core-data/src/lock-unlock.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', + '@wordpress/core-data' + ); diff --git a/packages/core-data/src/private-apis.js b/packages/core-data/src/private-apis.js index 91bf30792c970a..443db97957285d 100644 --- a/packages/core-data/src/private-apis.js +++ b/packages/core-data/src/private-apis.js @@ -1,10 +1,10 @@ /** - * WordPress dependencies + * Internal dependencies */ -import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; +import { useEntityRecordsWithPermissions } from './hooks/use-entity-records'; +import { lock } from './lock-unlock'; -export const { lock, unlock } = - __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', - '@wordpress/core-data' - ); +export const privateApis = {}; +lock( privateApis, { + useEntityRecordsWithPermissions, +} ); diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 6280bb96319634..9a9b2ef5100784 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -50,3 +50,26 @@ export const getBlockPatternsForPostType = createRegistrySelector( () => [ select( STORE_NAME ).getBlockPatterns() ] ) ); + +/** + * Returns the entity records permissions for the given entity record ids. + */ +export const getEntityRecordsPermissions = createRegistrySelector( ( select ) => + createSelector( + ( state: State, kind: string, name: string, ids: string[] ) => { + return ids.map( ( id ) => ( { + delete: select( STORE_NAME ).canUser( 'delete', { + kind, + name, + id, + } ), + update: select( STORE_NAME ).canUser( 'update', { + kind, + name, + id, + } ), + } ) ); + }, + ( state ) => [ state.userPermissions ] + ) +); diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js index ff25fbcd1962fb..68b8461929eb1d 100644 --- a/packages/edit-site/src/components/post-list/index.js +++ b/packages/edit-site/src/components/post-list/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { useEntityRecords, store as coreStore } from '@wordpress/core-data'; +import { + store as coreStore, + privateApis as coreDataPrivateApis, +} from '@wordpress/core-data'; import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { useSelect, useDispatch } from '@wordpress/data'; @@ -33,6 +36,7 @@ import usePostFields from '../post-fields'; const { usePostActions } = unlock( editorPrivateApis ); const { useLocation, useHistory } = unlock( routerPrivateApis ); +const { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis ); const EMPTY_ARRAY = []; function useView( postType ) { @@ -199,7 +203,7 @@ export default function PostList( { postType } ) { isResolving: isLoadingMainEntities, totalItems, totalPages, - } = useEntityRecords( 'postType', postType, queryArgs ); + } = useEntityRecordsWithPermissions( 'postType', postType, queryArgs ); const ids = records?.map( ( record ) => getItemId( record ) ) ?? []; const prevIds = usePrevious( ids ) ?? []; From 1425f949a67ae96c31f68f2b7cc40bb4d3cc5c99 Mon Sep 17 00:00:00 2001 From: James Koster <james@jameskoster.co.uk> Date: Fri, 26 Jul 2024 11:26:25 +0100 Subject: [PATCH 17/26] Update field line height across grid / list layouts (#63945) Co-authored-by: jameskoster <jameskoster@git.wordpress.org> Co-authored-by: richtabor <richtabor@git.wordpress.org> --- packages/dataviews/src/layouts/grid/style.scss | 6 ++++++ packages/dataviews/src/layouts/list/style.scss | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/src/layouts/grid/style.scss b/packages/dataviews/src/layouts/grid/style.scss index 91f0e2b8a381c9..44841e7db0e19b 100644 --- a/packages/dataviews/src/layouts/grid/style.scss +++ b/packages/dataviews/src/layouts/grid/style.scss @@ -61,6 +61,12 @@ padding: 0 0 $grid-unit-15; } + .dataviews-view-grid__field-value:not(:empty) { + min-height: $grid-unit-30; + line-height: $grid-unit-05 * 5; + padding-top: $grid-unit-05 / 2; + } + .dataviews-view-grid__field { align-items: flex-start; min-height: $grid-unit-30; diff --git a/packages/dataviews/src/layouts/list/style.scss b/packages/dataviews/src/layouts/list/style.scss index 09f29a73b74d2d..5b8f764d012a22 100644 --- a/packages/dataviews/src/layouts/list/style.scss +++ b/packages/dataviews/src/layouts/list/style.scss @@ -179,7 +179,10 @@ } .dataviews-view-list__field-value { - line-height: $grid-unit-05 * 6; + min-height: $grid-unit-30; + line-height: $grid-unit-05 * 5; + display: flex; + align-items: center; } } From 3c53a6ad25400f9f42e947f06025c147929f63f0 Mon Sep 17 00:00:00 2001 From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:11:12 +0530 Subject: [PATCH 18/26] Add color support in Categories List block (#63950) Co-authored-by: akasunil <sunil25393@git.wordpress.org> Co-authored-by: carolinan <poena@git.wordpress.org> --- packages/block-library/src/categories/block.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/block-library/src/categories/block.json b/packages/block-library/src/categories/block.json index 7f74befa3b6816..f192087dfb4617 100644 --- a/packages/block-library/src/categories/block.json +++ b/packages/block-library/src/categories/block.json @@ -62,6 +62,18 @@ }, "interactivity": { "clientNavigation": true + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": true, + "color": true, + "width": true, + "style": true + } } }, "editorStyle": "wp-block-categories-editor", From 062efa7d6a1cff7bc290595ac35aea5353a05364 Mon Sep 17 00:00:00 2001 From: Karol Manijak <20098064+kmanijak@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:55:46 +0200 Subject: [PATCH 19/26] Make Query Loop settings more intuitive with a ToggleGroup and simplified help text (#63739) * Replace Toggle with ToggleGroup for inherit control in Query Loop * Update a test based on Inherit query from template control * Simplify ternary operator * Update controls description in Queryn Loop block * remove const for labels used once * Add __next40pxDefaultSize --------- Co-authored-by: kmanijak <karolmanijak@git.wordpress.org> Co-authored-by: richtabor <richtabor@git.wordpress.org> Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: jameskoster <jameskoster@git.wordpress.org> --- .../query/edit/inspector-controls/index.js | 41 +++++++++++++------ .../specs/editor/various/is-typing.spec.js | 2 +- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index cd6b7a262c0081..81c21a236322ce 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -6,7 +6,6 @@ import { TextControl, SelectControl, RangeControl, - ToggleControl, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, Notice, @@ -103,9 +102,9 @@ export default function QueryInspectorControls( props ) { const showInheritControl = isControlAllowed( allowedControls, 'inherit' ); const showPostTypeControl = ! inherit && isControlAllowed( allowedControls, 'postType' ); - const postTypeControlLabel = __( 'Content type' ); + const postTypeControlLabel = __( 'Post type' ); const postTypeControlHelp = __( - 'WordPress contains different types of content you can filter by. Posts and pages are the default types, but plugins could add more.' + 'Select the type of content to display: posts, pages, or custom post types.' ); const showColumnsControl = false; const showOrderControl = @@ -146,17 +145,33 @@ export default function QueryInspectorControls( props ) { { showSettingsPanel && ( <PanelBody title={ __( 'Settings' ) }> { showInheritControl && ( - <ToggleControl - __nextHasNoMarginBottom - label={ __( 'Inherit query from template' ) } - help={ __( - 'Enable to use the global query context that is set with the current template, such as an archive or search. Disable to customize the settings independently.' - ) } - checked={ !! inherit } - onChange={ ( value ) => - setQuery( { inherit: !! value } ) + <ToggleGroupControl + __next40pxDefaultSize + label={ __( 'Query type' ) } + isBlock + onChange={ ( value ) => { + setQuery( { inherit: !! value } ); + } } + help={ + inherit + ? __( + 'Display a list of posts or custom post types based on the current template.' + ) + : __( + 'Display a list of posts or custom post types based on specific criteria.' + ) } - /> + value={ !! inherit } + > + <ToggleGroupControlOption + value + label={ __( 'Default' ) } + /> + <ToggleGroupControlOption + value={ false } + label={ __( 'Custom' ) } + /> + </ToggleGroupControl> ) } { showPostTypeControl && ( postTypesSelectOptions.length > 2 ? ( diff --git a/test/e2e/specs/editor/various/is-typing.spec.js b/test/e2e/specs/editor/various/is-typing.spec.js index e2c65f01928e04..10ac90d1084127 100644 --- a/test/e2e/specs/editor/various/is-typing.spec.js +++ b/test/e2e/specs/editor/various/is-typing.spec.js @@ -54,7 +54,7 @@ test.describe( 'isTyping', () => { .click(); await editor.openDocumentSettingsSidebar(); - await page.getByLabel( 'Inherit query from template' ).click(); + await page.getByLabel( 'Custom' ).click(); // Moving the mouse shows the toolbar. await editor.showBlockToolbar(); From 55e79d1736092ac14075463565076a4358a6c31b Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 26 Jul 2024 23:06:16 +0900 Subject: [PATCH 20/26] Fix deprecated sass usage (#63990) Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: jameskoster <jameskoster@git.wordpress.org> --- packages/dataviews/src/layouts/grid/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dataviews/src/layouts/grid/style.scss b/packages/dataviews/src/layouts/grid/style.scss index 44841e7db0e19b..6fe99642784dc9 100644 --- a/packages/dataviews/src/layouts/grid/style.scss +++ b/packages/dataviews/src/layouts/grid/style.scss @@ -64,7 +64,7 @@ .dataviews-view-grid__field-value:not(:empty) { min-height: $grid-unit-30; line-height: $grid-unit-05 * 5; - padding-top: $grid-unit-05 / 2; + padding-top: math.div($grid-unit-05, 2); } .dataviews-view-grid__field { From 688da865b12058239fb74ebab9f9af522d0ff517 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 26 Jul 2024 23:06:52 +0900 Subject: [PATCH 21/26] Image Block Lightbox: Fix warning error when resizing (#63995) * Image Block: Fix warning error when resizing * Try another approach Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: luisherranz <luisherranz@git.wordpress.org> --- packages/block-library/src/image/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 7010e9b7b2219e..0bc0dfaacea1a2 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -188,7 +188,7 @@ const { state, actions, callbacks } = store( }, callbacks: { setOverlayStyles() { - if ( ! state.currentImage.imageRef ) { + if ( ! state.overlayEnabled ) { return; } From 41d5ff2e221fe65529c2c6a437c422770e6c59d7 Mon Sep 17 00:00:00 2001 From: Ben Dwyer <ben@scruffian.com> Date: Fri, 26 Jul 2024 16:42:08 +0100 Subject: [PATCH 22/26] Remove an unnecessary wrapper component (#63989) Co-authored-by: scruffian <scruffian@git.wordpress.org> Co-authored-by: ellatrix <ellatrix@git.wordpress.org> --- .../pattern-category-preview-panel.js | 25 ------------------- .../src/components/inserter/menu.js | 4 +-- 2 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js deleted file mode 100644 index 505f8d14863729..00000000000000 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-preview-panel.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Internal dependencies - */ -import { PatternCategoryPreviews } from './pattern-category-previews'; - -export function PatternCategoryPreviewPanel( { - rootClientId, - onInsert, - onHover, - category, - showTitlesAsTooltip, - patternFilter, -} ) { - return ( - <PatternCategoryPreviews - key={ category.name } - rootClientId={ rootClientId } - onInsert={ onInsert } - onHover={ onHover } - category={ category } - showTitlesAsTooltip={ showTitlesAsTooltip } - patternFilter={ patternFilter } - /> - ); -} diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 17a98570843014..e0bc29d62e1b9a 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -26,7 +26,7 @@ import Tips from './tips'; import InserterPreviewPanel from './preview-panel'; import BlockTypesTab from './block-types-tab'; import BlockPatternsTab from './block-patterns-tab'; -import { PatternCategoryPreviewPanel } from './block-patterns-tab/pattern-category-preview-panel'; +import { PatternCategoryPreviews } from './block-patterns-tab/pattern-category-previews'; import { MediaTab, MediaCategoryPanel } from './media-tab'; import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; @@ -246,7 +246,7 @@ function InserterMenu( selectedCategory={ selectedPatternCategory } > { showPatternPanel && ( - <PatternCategoryPreviewPanel + <PatternCategoryPreviews rootClientId={ destinationRootClientId } onInsert={ onInsertPattern } onHover={ onHoverPattern } From c2bf14fb0ae6e6f6aacf02eedadc5d84abe47215 Mon Sep 17 00:00:00 2001 From: Lena Morita <lena@jaguchi.com> Date: Sat, 27 Jul 2024 00:57:36 +0900 Subject: [PATCH 23/26] CustomSelectControl: Restore `describedBy` functionality (#63957) * CustomSelectControl: Restore `describedBy` functionality * Add default description * Add changelog Unlinked contributors: rohitmathur-7. Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: tyxla <tyxla@git.wordpress.org> Co-authored-by: afercia <afercia@git.wordpress.org> --- packages/components/CHANGELOG.md | 4 ++ .../src/custom-select-control/index.tsx | 68 +++++++++++++------ .../src/custom-select-control/test/index.tsx | 22 ++++++ 3 files changed, 74 insertions(+), 20 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 6c32534598fb5c..f8666e96a20a15 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- `CustomSelectControl`: Restore `describedBy` functionality ([#63957](https://github.com/WordPress/gutenberg/pull/63957)). + ### Internal - `DropdownMenuV2`: break menu item help text on multiple lines for better truncation. ([#63916](https://github.com/WordPress/gutenberg/pull/63916)). diff --git a/packages/components/src/custom-select-control/index.tsx b/packages/components/src/custom-select-control/index.tsx index 5bce6afc4d92b6..8735b8d320991e 100644 --- a/packages/components/src/custom-select-control/index.tsx +++ b/packages/components/src/custom-select-control/index.tsx @@ -4,6 +4,12 @@ import * as Ariakit from '@ariakit/react'; import clsx from 'clsx'; +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; +import { __, sprintf } from '@wordpress/i18n'; + /** * Internal dependencies */ @@ -11,6 +17,7 @@ import _CustomSelect from '../custom-select-control-v2/custom-select'; import CustomSelectItem from '../custom-select-control-v2/item'; import * as Styled from '../custom-select-control-v2/styles'; import type { CustomSelectProps } from './types'; +import { VisuallyHidden } from '../visually-hidden'; function useDeprecatedProps( { __experimentalShowSelectedHint, @@ -35,6 +42,15 @@ function applyOptionDeprecations( { }; } +function getDescribedBy( currentValue: string, describedBy?: string ) { + if ( describedBy ) { + return describedBy; + } + + // translators: %s: The selected option. + return sprintf( __( 'Currently selected: %s' ), currentValue ); +} + function CustomSelectControl( props: CustomSelectProps ) { const { __next40pxDefaultSize = false, @@ -48,8 +64,13 @@ function CustomSelectControl( props: CustomSelectProps ) { ...restProps } = useDeprecatedProps( props ); + const descriptionId = useInstanceId( + CustomSelectControl, + 'custom-select-control__description' + ); + // Forward props + store from v2 implementation - const store = Ariakit.useSelectStore( { + const store = Ariakit.useSelectStore< string >( { async setValue( nextValue ) { const nextOption = options.find( ( item ) => item.name === nextValue @@ -117,9 +138,9 @@ function CustomSelectControl( props: CustomSelectProps ) { ); } ); - const renderSelectedValueHint = () => { - const { value: currentValue } = store.getState(); + const { value: currentValue } = store.getState(); + const renderSelectedValueHint = () => { const selectedOptionHint = options ?.map( applyOptionDeprecations ) ?.find( ( { name } ) => currentValue === name )?.hint; @@ -153,23 +174,30 @@ function CustomSelectControl( props: CustomSelectProps ) { } )(); return ( - <_CustomSelect - aria-describedby={ describedBy } - renderSelectedValue={ - showSelectedHint ? renderSelectedValueHint : undefined - } - size={ translatedSize } - store={ store } - className={ clsx( - // Keeping the classname for legacy reasons - 'components-custom-select-control', - classNameProp - ) } - isLegacy - { ...restProps } - > - { children } - </_CustomSelect> + <> + <_CustomSelect + aria-describedby={ descriptionId } + renderSelectedValue={ + showSelectedHint ? renderSelectedValueHint : undefined + } + size={ translatedSize } + store={ store } + className={ clsx( + // Keeping the classname for legacy reasons + 'components-custom-select-control', + classNameProp + ) } + isLegacy + { ...restProps } + > + { children } + </_CustomSelect> + <VisuallyHidden> + <span id={ descriptionId }> + { getDescribedBy( currentValue, describedBy ) } + </span> + </VisuallyHidden> + </> ); } diff --git a/packages/components/src/custom-select-control/test/index.tsx b/packages/components/src/custom-select-control/test/index.tsx index 73dcb039df3f1b..4bc96f318138c5 100644 --- a/packages/components/src/custom-select-control/test/index.tsx +++ b/packages/components/src/custom-select-control/test/index.tsx @@ -670,5 +670,27 @@ describe.each( [ expect( currentSelectedItem ).not.toHaveFocus(); expect( onBlurMock ).toHaveBeenCalledTimes( 1 ); } ); + + it( 'should render the describedBy text when specified', async () => { + const describedByText = 'My description.'; + + render( + <Component { ...props } describedBy={ describedByText } /> + ); + + expect( + screen.getByRole( 'combobox' ) + ).toHaveAccessibleDescription( describedByText ); + } ); + + it( 'should render the default ARIA description when describedBy is not specified', async () => { + render( <Component { ...props } /> ); + + expect( + screen.getByRole( 'combobox' ) + ).toHaveAccessibleDescription( + `Currently selected: ${ props.options[ 0 ].name }` + ); + } ); } ); } ); From 285a304e03ec895ca311cab15d81061d8a8c3a91 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Sat, 27 Jul 2024 06:29:36 +1000 Subject: [PATCH 24/26] Column: Add border radius support (#63924) Unlinked contributors: gabrieltogan, porg, bradhogan. Co-authored-by: aaronrobertshaw <aaronrobertshaw@git.wordpress.org> Co-authored-by: carolinan <poena@git.wordpress.org> Co-authored-by: richtabor <richtabor@git.wordpress.org> Co-authored-by: mikemcalister <mmcalister@git.wordpress.org> Co-authored-by: maurodf0 <maurodf@git.wordpress.org> Co-authored-by: bgardner <bgardner@git.wordpress.org> Co-authored-by: deryckoe <deryck@git.wordpress.org> --- packages/block-library/src/column/block.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json index 0857abb47ffdc0..33bd528b356880 100644 --- a/packages/block-library/src/column/block.json +++ b/packages/block-library/src/column/block.json @@ -48,10 +48,12 @@ }, "__experimentalBorder": { "color": true, + "radius": true, "style": true, "width": true, "__experimentalDefaultControls": { "color": true, + "radius": true, "style": true, "width": true } From 93f80f67d81fe26895edc799f8c78a5970022dcc Mon Sep 17 00:00:00 2001 From: Shail Mehta <shailmehta25@gmail.com> Date: Sat, 27 Jul 2024 09:26:39 +0530 Subject: [PATCH 25/26] Corrected @deprecated doc Order (#64013) Co-authored-by: shail-mehta <shailu25@git.wordpress.org> Co-authored-by: t-hamano <wildworks@git.wordpress.org> --- lib/experimental/script-modules.php | 9 ++++++--- .../block-editor/src/components/use-settings/index.js | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php index 709ab322f63a3e..0093c2e974568f 100644 --- a/lib/experimental/script-modules.php +++ b/lib/experimental/script-modules.php @@ -165,11 +165,12 @@ function gutenberg_register_view_module_ids_rest_field() { * Registers the module if no module with that module identifier has already * been registered. * + * @deprecated 17.6.0 gutenberg_register_module is deprecated. Please use wp_register_script_module instead. + * * @param string $module_identifier The identifier of the module. Should be unique. It will be used in the final import map. * @param string $src Full URL of the module, or path of the script relative to the WordPress root directory. * @param array $dependencies Optional. An array of module identifiers of the dependencies of this module. The dependencies can be strings or arrays. If they are arrays, they need an `id` key with the module identifier, and can contain an `import` key with either `static` or `dynamic`. By default, dependencies that don't contain an import are considered static. * @param string|false|null $version Optional. String specifying module version number. Defaults to false. It is added to the URL as a query string for cache busting purposes. If $version is set to false, the version number is the currently installed WordPress version. If $version is set to null, no version is added. - * @deprecated 17.6.0 gutenberg_register_module is deprecated. Please use wp_register_script_module instead. */ function gutenberg_register_module( $module_identifier, $src = '', $dependencies = array(), $version = false ) { _deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_register_script_module' ); @@ -179,8 +180,9 @@ function gutenberg_register_module( $module_identifier, $src = '', $dependencies /** * Marks the module to be enqueued in the page. * - * @param string $module_identifier The identifier of the module. * @deprecated 17.6.0 gutenberg_enqueue_module is deprecated. Please use wp_enqueue_script_module instead. + * + * @param string $module_identifier The identifier of the module. */ function gutenberg_enqueue_module( $module_identifier ) { _deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_enqueue_script_module' ); @@ -190,8 +192,9 @@ function gutenberg_enqueue_module( $module_identifier ) { /** * Unmarks the module so it is not longer enqueued in the page. * - * @param string $module_identifier The identifier of the module. * @deprecated 17.6.0 gutenberg_dequeue_module is deprecated. Please use wp_dequeue_script_module instead. + * + * @param string $module_identifier The identifier of the module. */ function gutenberg_dequeue_module( $module_identifier ) { _deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_dequeue_script_module' ); diff --git a/packages/block-editor/src/components/use-settings/index.js b/packages/block-editor/src/components/use-settings/index.js index b0410b404d5e98..e356260c2d6b38 100644 --- a/packages/block-editor/src/components/use-settings/index.js +++ b/packages/block-editor/src/components/use-settings/index.js @@ -43,9 +43,10 @@ export function useSettings( ...paths ) { * It looks up the setting first in the block instance hierarchy. * If none is found, it'll look it up in the block editor settings. * + * @deprecated 6.5.0 Use useSettings instead. + * * @param {string} path The path to the setting. * @return {any} Returns the value defined for the setting. - * @deprecated 6.5.0 Use useSettings instead. * @example * ```js * const isEnabled = useSetting( 'typography.dropCap' ); From 159d01a01b61ee76159ab67bc94d0d7cb3d90b35 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Sat, 27 Jul 2024 13:02:31 +0900 Subject: [PATCH 26/26] Post Editor: Prevent popover from being hidden by metabox (#63939) * Post Editor: Prevent popover from being hidden by metabox * Use `.interface-interface-skeleton__content` instead of `.interface-interface-skeleton__body` Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: stokesman <presstoke@git.wordpress.org> Co-authored-by: talldan <talldanwp@git.wordpress.org> Co-authored-by: Rishit30G <rishit30g@git.wordpress.org> Co-authored-by: dhananjaykuber <dhananjaykuber@git.wordpress.org> --- packages/editor/src/components/editor-interface/style.scss | 5 +++++ packages/editor/src/components/visual-editor/style.scss | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/editor-interface/style.scss b/packages/editor/src/components/editor-interface/style.scss index 77f621acc93510..05b23fe2304dd8 100644 --- a/packages/editor/src/components/editor-interface/style.scss +++ b/packages/editor/src/components/editor-interface/style.scss @@ -2,6 +2,11 @@ height: $header-height + $border-width; } +.editor-editor-interface .interface-interface-skeleton__content { + // Make this a stacking context to contain the z-index of children elements. + isolation: isolate; +} + .editor-visual-editor { flex: 1 0 auto; } diff --git a/packages/editor/src/components/visual-editor/style.scss b/packages/editor/src/components/visual-editor/style.scss index 2d7ed665a957f3..18a61827d573da 100644 --- a/packages/editor/src/components/visual-editor/style.scss +++ b/packages/editor/src/components/visual-editor/style.scss @@ -2,8 +2,6 @@ position: relative; display: block; background-color: $gray-300; - // Make this a stacking context to contain the z-index of children elements. - isolation: isolate; // Centralize the editor horizontally (flex-direction is column). align-items: center;