From 236250e3fa04ad7337c305540f76ed619b0dde98 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Thu, 8 Aug 2024 11:59:28 +0200
Subject: [PATCH 001/126] Components: add "Naming conventions" section (#63714)
* Use update compound suggested notation
* Remove reference to item group as it may not reflect anymore all recent best practices
* Tweak part about experimental APIs
* Fix spacing
* Linter auto-formatting
* Add Naming Conventions section
* Update guidelines to use overloaded convention, remove references to monolithic components
* Added more details to the JSX example
* Grammar
* Split unforwarded component from forwardRef call
* Fix comment + auto-format
* Add section on JSDocs + IntelliSense requirements + recommended best practices
* Use named functions without attributing them to const
---
Co-authored-by: ciampo
Co-authored-by: tyxla
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: DaniGuardiola
Co-authored-by: diegohaz
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
Co-authored-by: aaronrobertshaw
Co-authored-by: noisysocks
---
packages/components/CONTRIBUTING.md | 219 ++++++++++++++++++++--------
1 file changed, 155 insertions(+), 64 deletions(-)
diff --git a/packages/components/CONTRIBUTING.md b/packages/components/CONTRIBUTING.md
index f1245da7a409c6..57a9f2fb049a24 100644
--- a/packages/components/CONTRIBUTING.md
+++ b/packages/components/CONTRIBUTING.md
@@ -6,20 +6,20 @@ The following is a set of guidelines for contributing to the `@wordpress/compone
This set of guidelines should apply especially to newly introduced components. In fact, while these guidelines should also be retroactively applied to existing components, it is sometimes impossible to do so for legacy/compatibility reasons.
-For an example of a component that follows these requirements, take a look at [`ItemGroup`](/packages/components/src/item-group).
-- [Introducing new components](#introducing-new-components)
-- [Compatibility](#compatibility)
-- [Compound components](#compound-components)
-- [Components & Hooks](#components--hooks)
-- [TypeScript](#typescript)
-- [Styling](#styling)
-- [Context system](#context-system)
-- [Unit tests](#unit-tests)
-- [Storybook](#storybook)
-- [Documentation](#documentation)
-- [README example](#README-example)
-- [Folder structure](#folder-structure)
-- [Component versioning](#component-versioning)
+- [Introducing new components](#introducing-new-components)
+- [Compatibility](#compatibility)
+- [Compound components](#compound-components)
+- [Components & Hooks](#components--hooks)
+- [Naming Conventions](#naming-conventions)
+- [TypeScript](#typescript)
+- [Styling](#styling)
+- [Context system](#context-system)
+- [Unit tests](#unit-tests)
+- [Storybook](#storybook)
+- [Documentation](#documentation)
+- [README example](#README-example)
+- [Folder structure](#folder-structure)
+- [Component versioning](#component-versioning)
## Introducing new components
@@ -95,13 +95,13 @@ In these situations, one possible approach is to "soft-deprecate" a given legacy
2. Updating all places in Gutenberg that use that API.
3. Adding deprecation warnings (only after the previous point is completed, otherwise the Browser Console will be polluted by all those warnings and some e2e tests may fail).
-When adding new components or new props to existing components, it's recommended to prefix them with `__unstable` or `__experimental` until they're stable enough to be exposed as part of the public API.
+When adding new components or new props to existing components, it's recommended to create a [private version](/packages/private-apis/README.md)) of the component until the changes are stable enough to be exposed as part of the public API.
### Learn more
-- [How to preserve backward compatibility for a React Component](/docs/contributors/code/backward-compatibility.md#how-to-preserve-backward-compatibility-for-a-react-component)
-- [Experimental and Unstable APIs](/docs/contributors/code/coding-guidelines.md#experimental-and-unstable-apis)
-- [Deprecating styles](#deprecating-styles)
+- [How to preserve backward compatibility for a React Component](/docs/contributors/code/backward-compatibility.md#how-to-preserve-backward-compatibility-for-a-react-component)
+- [Experimental and Unstable APIs](/docs/contributors/code/coding-guidelines.md#legacy-experimental-apis-plugin-only-apis-and-private-apis)
+- [Deprecating styles](#deprecating-styles)
+## Naming Conventions
+
+It is recommended that compound components use dot notation to separate the namespace from the individual component names. The top-level compound component should be called the namespace (no dot notation).
+
+Dedicated React context should also use dot notation, while hooks should not.
+
+When exporting compound components and preparing them to be consumed, it is important that:
+
+- the JSDocs appear correctly in IntelliSense;
+- the top-level component's JSDoc appears in the Storybook docs page;
+- the top-level and subcomponent's prop types appear correctly in the Storybook props table.
+
+To meet the above requirements, we recommend:
+
+- using `Object.assign()` to add subcomponents as properties of the top-level component;
+- using named functions for all components;
+- setting explicitly the `displayName` on all subcomponents;
+- adding the top-level JSDoc to the result of the `Object.assign` call;
+- adding inline subcomponent JSDocs inside the `Object.assign` call.
+
+The following example implements all of the above recommendations.
+
+```tsx
+//=======================
+// Component.tsx
+//=======================
+import { forwardRef, createContext } from '@wordpress/element';
+
+function UnforwardedTopLevelComponent( props, ref ) {
+ /* ... */
+}
+const TopLevelComponent = forwardRef( UnforwardedTopLevelComponent );
+
+function UnforwardedSubComponent( props, ref ) {
+ /* ... */
+}
+const SubComponent = forwardRef( UnforwardedSubComponent );
+SubComponent.displayName = 'Component.SubComponent';
+
+const Context = createContext();
+
+/** The top-level component's JSDoc. */
+export const Component = Object.assign( TopLevelComponent, {
+ /** The subcomponent's JSDoc. */
+ SubComponent,
+ /** The context's JSDoc. */
+ Context,
+} );
+
+/** The hook's JSDoc. */
+export function useComponent() {
+ /* ... */
+}
+
+//=======================
+// App.tsx
+//=======================
+import { Component, useComponent } from '@wordpress/components';
+import { useContext } from '@wordpress/element';
+
+function CompoundComponentExample() {
+ return (
+
+
+
+ );
+}
+
+function ContextProviderExample() {
+ return (
+
+ { /* React tree */ }
+
+ );
+}
+
+function ContextConsumerExample() {
+ const componentContext = useContext( Component.Context );
+
+ // etc
+}
+
+function HookExample() {
+ const hookReturnValue = useComponent();
+
+ // etc.
+}
+```
+
## TypeScript
We strongly encourage using TypeScript for all new components.
@@ -278,8 +363,10 @@ function UnconnectedMyComponent(
// parameter (`div` in this example)
// - the special `as` prop (which marks the component as polymorphic),
// unless the third parameter is `false`
- props: WordPressComponentProps< ComponentOwnProps, 'div', true >
-) { /* ... */ }
+ props: WordPressComponentProps< ComponentOwnProps, 'div', true >
+) {
+ /* ... */
+}
```
### Considerations for the docgen
@@ -287,10 +374,15 @@ function UnconnectedMyComponent(
Make sure you have a **named** export for the component, not just the default export ([example](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/divider/component.tsx)). This ensures that the docgen can properly extract the types data. The naming should be so that the connected/forwarded component has the plain component name (`MyComponent`), and the raw component is prefixed (`UnconnectedMyComponent` or `UnforwardedMyComponent`). This makes the component's `displayName` look nicer in React devtools and in the autogenerated Storybook code snippets.
```js
-function UnconnectedMyComponent() { /* ... */ }
+function UnconnectedMyComponent() {
+ /* ... */
+}
// 👇 Without this named export, the docgen will not work!
-export const MyComponent = contextConnect( UnconnectedMyComponent, 'MyComponent' );
+export const MyComponent = contextConnect(
+ UnconnectedMyComponent,
+ 'MyComponent'
+);
export default MyComponent;
```
@@ -314,16 +406,15 @@ Changing the styles of a non-experimental component must be done with care. To p
import deprecated from '@wordpress/deprecated';
import { Wrapper } from './styles.ts';
-function MyComponent({ __nextHasNoOuterMargins = false }) {
+function MyComponent( { __nextHasNoOuterMargins = false } ) {
if ( ! __nextHasNoOuterMargins ) {
deprecated( 'Outer margin styles for wp.components.MyComponent', {
since: '6.0',
version: '6.2', // Set a reasonable grace period depending on impact
- hint:
- 'Set the `__nextHasNoOuterMargins` prop to true to start opting into the new styles, which will become the default in a future version.',
+ hint: 'Set the `__nextHasNoOuterMargins` prop to true to start opting into the new styles, which will become the default in a future version.',
} );
}
- return
+ return ;
}
```
@@ -331,7 +422,7 @@ Styles should be structured so the deprecated styles are cleanly encapsulated, a
```js
// styles.ts
-const deprecatedMargins = ({ __nextHasNoOuterMargins }) => {
+const deprecatedMargins = ( { __nextHasNoOuterMargins } ) => {
if ( ! __nextHasNoOuterMargins ) {
return css`
margin: 8px;
@@ -342,7 +433,7 @@ const deprecatedMargins = ({ __nextHasNoOuterMargins }) => {
export const Wrapper = styled.div`
margin: 0;
- ${deprecatedMargins}
+ ${ deprecatedMargins }
`;
```
@@ -358,14 +449,14 @@ Not all style changes justify a formal deprecation process. The main thing to lo
##### DOES need formal deprecation
-- Removing an outer margin.
-- Substantial changes to width/height, such as adding or removing a size restriction.
+- Removing an outer margin.
+- Substantial changes to width/height, such as adding or removing a size restriction.
##### DOES NOT need formal deprecation
-- Breakage only occurs in non-standard usage, such as when the consumer is overriding component internals.
-- Minor layout shifts of a few pixels.
-- Internal layout changes of a higher-level component.
+- Breakage only occurs in non-standard usage, such as when the consumer is overriding component internals.
+- Minor layout shifts of a few pixels.
+- Internal layout changes of a higher-level component.
## Context system
@@ -373,9 +464,9 @@ The `@wordpress/components` context system is based on [React's `Context` API](h
Components can use this system via a couple of functions:
-- they can provide values using a shared `ContextSystemProvider` component
-- they can connect to the Context via `contextConnect`
-- they can read the "computed" values from the context via `useContextSystem`
+- they can provide values using a shared `ContextSystemProvider` component
+- they can connect to the Context via `contextConnect`
+- they can read the "computed" values from the context via `useContextSystem`
An example of how this is used can be found in the [`Card` component family](/packages/components/src/card). For example, this is how the `Card` component injects the `size` and `isBorderless` props down to its `CardBody` subcomponent — which makes it use the correct spacing and border settings "auto-magically".
@@ -400,11 +491,7 @@ export function useCard( props ) {
import { contextConnect, ContextSystemProvider } from '../../context';
function Card( props, forwardedRef ) {
- const {
- size,
- isBorderless,
- ...otherComputedHookProps
- } = useCard( props );
+ const { size, isBorderless, ...otherComputedHookProps } = useCard( props );
// [...]
@@ -441,7 +528,10 @@ export function useCardBody( props ) {
// If a `CardBody` component is rendered as a child of a `Card` component, the value of
// the `size` prop will be the one set by the parent `Card` component via the Context
// System (unless the prop gets explicitely set on the `CardBody` component).
- const { size = 'medium', ...otherDerivedProps } = useContextSystem( props, 'CardBody' );
+ const { size = 'medium', ...otherDerivedProps } = useContextSystem(
+ props,
+ 'CardBody'
+ );
// [...]
@@ -457,7 +547,7 @@ Please refer to the [JavaScript Testing Overview docs](/docs/contributors/code/t
All new components should add stories to the project's [Storybook](https://storybook.js.org/). Each [story](https://storybook.js.org/docs/react/get-started/whats-a-story) captures the rendered state of a UI component in isolation. This greatly simplifies working on a given component, while also serving as an interactive form of documentation.
-A component's story should be showcasing its different states — for example, the different variants of a `Button`:
+A component's story should be showcasing its different states — for example, the different variants of a `Button`:
```jsx
import Button from '../';
@@ -543,6 +633,7 @@ Prop description. With a new line before and after the description and before an
Add this section when there are props that are drilled down into an internal component. See [ClipboardButton](/packages/components/src/clipboard-button/README.md) for an example.
+
## Context
See examples for this section for the [ItemGroup](/packages/components/src/item-group/item-group/README.md#context) and [`Card`](/packages/components/src/card/card/README.md#context) components.
@@ -601,8 +692,8 @@ As the needs of the package evolve with time, sometimes we may opt to fully rewr
Here is some terminology that will be used in the upcoming sections:
-- "Legacy" component: the version(s) of the component that existsted on `trunk` before the rewrite;
-- API surface: the component's public APIs. It includes the list of components (and sub-components) exported from the package, their props, any associated React context. It does not include internal classnames and internal DOM structure of the components.
+- "Legacy" component: the version(s) of the component that existsted on `trunk` before the rewrite;
+- API surface: the component's public APIs. It includes the list of components (and subcomponents) exported from the package, their props, any associated React context. It does not include internal classnames and internal DOM structure of the components.
### Approaches
From 7661f457c867906d57ea19f05becef217a1ad31c Mon Sep 17 00:00:00 2001
From: Dave Smith
Date: Thu, 8 Aug 2024 11:01:34 +0100
Subject: [PATCH 002/126] Stop unwanted drag and drop operations within section
Patterns in Zoom Out mode (#64331)
* Fix conditionals
* Disable horizontal insertion points to prioritise dropping between sections
---
.../components/block-tools/insertion-point.js | 11 +++++++++++
.../src/components/inner-blocks/index.js | 19 ++++++++++---------
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js
index 9dac99e5e93124..469f7e53908cb4 100644
--- a/packages/block-editor/src/components/block-tools/insertion-point.js
+++ b/packages/block-editor/src/components/block-tools/insertion-point.js
@@ -38,6 +38,7 @@ function InbetweenInsertionPointPopover( {
isInserterShown,
isDistractionFree,
isNavigationMode,
+ isZoomOutMode,
} = useSelect( ( select ) => {
const {
getBlockOrder,
@@ -48,6 +49,7 @@ function InbetweenInsertionPointPopover( {
getNextBlockClientId,
getSettings,
isNavigationMode: _isNavigationMode,
+ __unstableGetEditorMode,
} = select( blockEditorStore );
const insertionPoint = getBlockInsertionPoint();
const order = getBlockOrder( insertionPoint.rootClientId );
@@ -79,6 +81,7 @@ function InbetweenInsertionPointPopover( {
isNavigationMode: _isNavigationMode(),
isDistractionFree: settings.isDistractionFree,
isInserterShown: insertionPoint?.__unstableWithInserter,
+ isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
};
}, [] );
const { getBlockEditingMode } = useSelect( blockEditorStore );
@@ -145,6 +148,14 @@ function InbetweenInsertionPointPopover( {
return null;
}
+ // Zoom out mode should only show the insertion point for the insert operation.
+ // Other operations such as "group" are when the editor tries to create a row
+ // block by grouping the block being dragged with the block it's being dropped
+ // onto.
+ if ( isZoomOutMode && operation !== 'insert' ) {
+ return null;
+ }
+
const orientationClassname =
orientation === 'horizontal' || operation === 'group'
? 'is-horizontal'
diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js
index 27e5064eeb6328..c8db9f8cebf905 100644
--- a/packages/block-editor/src/components/inner-blocks/index.js
+++ b/packages/block-editor/src/components/inner-blocks/index.js
@@ -206,13 +206,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) {
getSettings,
} = unlock( select( blockEditorStore ) );
let _isDropZoneDisabled;
- // In zoom out mode, we want to disable the drop zone for the sections.
- // The inner blocks belonging to the section drop zone is
- // already disabled by the blocks themselves being disabled.
- if ( __unstableGetEditorMode() === 'zoom-out' ) {
- const { sectionRootClientId } = unlock( getSettings() );
- _isDropZoneDisabled = clientId !== sectionRootClientId;
- }
+
if ( ! clientId ) {
return { isDropZoneDisabled: _isDropZoneDisabled };
}
@@ -225,8 +219,15 @@ export function useInnerBlocksProps( props = {}, options = {} ) {
const parentClientId = getBlockRootClientId( clientId );
const [ defaultLayout ] = getBlockSettings( clientId, 'layout' );
- if ( _isDropZoneDisabled !== undefined ) {
- _isDropZoneDisabled = blockEditingMode === 'disabled';
+ _isDropZoneDisabled = blockEditingMode === 'disabled';
+
+ if ( __unstableGetEditorMode() === 'zoom-out' ) {
+ // In zoom out mode, we want to disable the drop zone for the sections.
+ // The inner blocks belonging to the section drop zone is
+ // already disabled by the blocks themselves being disabled.
+ const { sectionRootClientId } = unlock( getSettings() );
+
+ _isDropZoneDisabled = clientId !== sectionRootClientId;
}
return {
From 098c1a2e45d3daa1353af6e26e9f518a06392865 Mon Sep 17 00:00:00 2001
From: Jarda Snajdr
Date: Thu, 8 Aug 2024 13:14:59 +0200
Subject: [PATCH 003/126] DataViews: abandon the ItemRecord type (#64367)
Co-authored-by: jsnajdr
Co-authored-by: youknowriad
---
packages/dataviews/src/normalize-fields.ts | 6 ++----
packages/dataviews/src/types.ts | 24 +++++++---------------
2 files changed, 9 insertions(+), 21 deletions(-)
diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts
index 8cd9051cbb1cc3..680749df5344a6 100644
--- a/packages/dataviews/src/normalize-fields.ts
+++ b/packages/dataviews/src/normalize-fields.ts
@@ -2,7 +2,7 @@
* Internal dependencies
*/
import getFieldTypeDefinition from './field-types';
-import type { Field, NormalizedField, ItemRecord } from './types';
+import type { Field, NormalizedField } from './types';
/**
* Apply default values and normalize the fields config.
@@ -17,9 +17,7 @@ export function normalizeFields< Item >(
const fieldTypeDefinition = getFieldTypeDefinition( field.type );
const getValue =
- field.getValue ||
- // @ts-ignore
- ( ( { item }: { item: ItemRecord } ) => item[ field.id ] );
+ field.getValue || ( ( { item } ) => ( item as any )[ field.id ] );
const sort =
field.sort ??
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 64580a089997d1..b0873e9c677f53 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -47,8 +47,6 @@ export type Operator =
| 'isAll'
| 'isNotAll';
-export type ItemRecord = Object;
-
export type FieldType = 'text' | 'integer';
export type ValidationContext = {
@@ -128,21 +126,13 @@ export type Field< Item > = {
* Filter config for the field.
*/
filterBy?: FilterByConfig | undefined;
-} & ( Item extends ItemRecord
- ? {
- /**
- * Callback used to retrieve the value of the field from the item.
- * Defaults to `item[ field.id ]`.
- */
- getValue?: ( args: { item: Item } ) => any;
- }
- : {
- /**
- * Callback used to retrieve the value of the field from the item.
- * Defaults to `item[ field.id ]`.
- */
- getValue: ( args: { item: Item } ) => any;
- } );
+
+ /**
+ * Callback used to retrieve the value of the field from the item.
+ * Defaults to `item[ field.id ]`.
+ */
+ getValue?: ( args: { item: Item } ) => any;
+};
export type NormalizedField< Item > = Field< Item > & {
label: string;
From 3a21676ea456e3484949475c9b654de3e252ac45 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Thu, 8 Aug 2024 13:16:01 +0200
Subject: [PATCH 004/126] DataViews Extensibility: Allow unregistering the
rename post action (#64366)
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
---
.../src/components/post-actions/actions.js | 132 +---------------
.../src/dataviews/actions/rename-post.tsx | 146 ++++++++++++++++++
.../editor/src/dataviews/actions/utils.ts | 14 +-
.../src/dataviews/store/private-actions.ts | 4 +-
packages/editor/src/dataviews/types.ts | 16 +-
5 files changed, 174 insertions(+), 138 deletions(-)
create mode 100644 packages/editor/src/dataviews/actions/rename-post.tsx
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 64afec2417cd05..490bab2cc2fa4b 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -14,7 +14,6 @@ import { parse } from '@wordpress/blocks';
import { DataForm } from '@wordpress/dataviews';
import {
Button,
- TextControl,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
@@ -23,7 +22,6 @@ import {
* Internal dependencies
*/
import {
- TEMPLATE_ORIGINS,
TEMPLATE_PART_POST_TYPE,
TEMPLATE_POST_TYPE,
PATTERN_POST_TYPE,
@@ -34,7 +32,7 @@ import { CreateTemplatePartModalContents } from '../create-template-part-modal';
import { getItemTitle } from '../../dataviews/actions/utils';
// Patterns.
-const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } =
+const { CreatePatternModalContents, useDuplicatePatternProps } =
unlock( patternsPrivateApis );
// TODO: this should be shared with other components (see post-fields in edit-site).
@@ -58,25 +56,6 @@ const formDuplicateAction = {
fields: [ 'title' ],
};
-/**
- * Check if a template is removable.
- *
- * @param {Object} template The template entity to check.
- * @return {boolean} Whether the template is removable.
- */
-function isTemplateRemovable( template ) {
- if ( ! template ) {
- return false;
- }
- // In patterns list page we map the templates parts to a different object
- // than the one returned from the endpoint. This is why we need to check for
- // two props whether is custom or has a theme file.
- return (
- template?.source === TEMPLATE_ORIGINS.custom &&
- ! template?.has_theme_file
- );
-}
-
const viewPostAction = {
id: 'view-post',
label: __( 'View' ),
@@ -128,112 +107,6 @@ const postRevisionsAction = {
},
};
-const renamePostAction = {
- id: 'rename-post',
- label: __( 'Rename' ),
- isEligible( post ) {
- if ( post.status === 'trash' ) {
- return false;
- }
- // Templates, template parts and patterns have special checks for renaming.
- if (
- ! [
- TEMPLATE_POST_TYPE,
- TEMPLATE_PART_POST_TYPE,
- ...Object.values( PATTERN_TYPES ),
- ].includes( post.type )
- ) {
- return post.permissions?.update;
- }
- // In the case of templates, we can only rename custom templates.
- if ( post.type === TEMPLATE_POST_TYPE ) {
- return (
- isTemplateRemovable( post ) &&
- post.is_custom &&
- post.permissions?.update
- );
- }
- // Make necessary checks for template parts and patterns.
- const isTemplatePart = post.type === TEMPLATE_PART_POST_TYPE;
- const isUserPattern = post.type === PATTERN_TYPES.user;
- // In patterns list page we map the templates parts to a different object
- // than the one returned from the endpoint. This is why we need to check for
- // two props whether is custom or has a theme file.
- const isCustomPattern =
- isUserPattern ||
- ( isTemplatePart && post.source === TEMPLATE_ORIGINS.custom );
- const hasThemeFile = post?.has_theme_file;
- return isCustomPattern && ! hasThemeFile && post.permissions?.update;
- },
- RenderModal: ( { items, closeModal, onActionPerformed } ) => {
- const [ item ] = items;
- const [ title, setTitle ] = useState( () => getItemTitle( item ) );
- const { editEntityRecord, saveEditedEntityRecord } =
- useDispatch( coreStore );
- const { createSuccessNotice, createErrorNotice } =
- useDispatch( noticesStore );
-
- async function onRename( event ) {
- event.preventDefault();
- try {
- await editEntityRecord( 'postType', item.type, item.id, {
- title,
- } );
- // Update state before saving rerenders the list.
- setTitle( '' );
- closeModal();
- // Persist edited entity.
- await saveEditedEntityRecord( 'postType', item.type, item.id, {
- throwOnError: true,
- } );
- createSuccessNotice( __( 'Name updated' ), {
- type: 'snackbar',
- } );
- onActionPerformed?.( items );
- } catch ( error ) {
- const errorMessage =
- error.message && error.code !== 'unknown_error'
- ? error.message
- : __( 'An error occurred while updating the name' );
- createErrorNotice( errorMessage, { type: 'snackbar' } );
- }
- }
-
- return (
-
- );
- },
-};
-
const useDuplicatePostAction = ( postType ) => {
const userCanCreatePost = useSelect(
( select ) => {
@@ -494,7 +367,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
const isPattern = postType === PATTERN_POST_TYPE;
const isLoaded = !! postTypeObject;
const supportsRevisions = !! postTypeObject?.supports?.revisions;
- const supportsTitle = !! postTypeObject?.supports?.title;
return useMemo( () => {
if ( ! isLoaded ) {
return [];
@@ -512,7 +384,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
userCanCreatePostType &&
duplicateTemplatePartAction,
isPattern && userCanCreatePostType && duplicatePatternAction,
- supportsTitle && renamePostAction,
...defaultActions,
].filter( Boolean );
// Filter actions based on provided context. If not provided
@@ -586,7 +457,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
onActionPerformed,
isLoaded,
supportsRevisions,
- supportsTitle,
context,
] );
}
diff --git a/packages/editor/src/dataviews/actions/rename-post.tsx b/packages/editor/src/dataviews/actions/rename-post.tsx
new file mode 100644
index 00000000000000..ef9da271111ea2
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/rename-post.tsx
@@ -0,0 +1,146 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
+import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
+// @ts-ignore
+import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
+import {
+ Button,
+ TextControl,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
+} from '@wordpress/components';
+import type { Action } from '@wordpress/dataviews';
+import { store as noticesStore } from '@wordpress/notices';
+
+/**
+ * Internal dependencies
+ */
+import {
+ TEMPLATE_ORIGINS,
+ TEMPLATE_PART_POST_TYPE,
+ TEMPLATE_POST_TYPE,
+} from '../../store/constants';
+import { unlock } from '../../lock-unlock';
+import {
+ getItemTitle,
+ isTemplateRemovable,
+ isTemplate,
+ isTemplatePart,
+} from './utils';
+import type { CoreDataError, PostWithPermissions } from '../types';
+
+// Patterns.
+const { PATTERN_TYPES } = unlock( patternsPrivateApis );
+
+const renamePost: Action< PostWithPermissions > = {
+ id: 'rename-post',
+ label: __( 'Rename' ),
+ isEligible( post ) {
+ if ( post.status === 'trash' ) {
+ return false;
+ }
+ // Templates, template parts and patterns have special checks for renaming.
+ if (
+ ! [
+ TEMPLATE_POST_TYPE,
+ TEMPLATE_PART_POST_TYPE,
+ ...Object.values( PATTERN_TYPES ),
+ ].includes( post.type )
+ ) {
+ return post.permissions?.update;
+ }
+
+ // In the case of templates, we can only rename custom templates.
+ if ( isTemplate( post ) ) {
+ return (
+ isTemplateRemovable( post ) &&
+ post.is_custom &&
+ post.permissions?.update
+ );
+ }
+
+ if ( isTemplatePart( post ) ) {
+ return (
+ post.source === TEMPLATE_ORIGINS.custom &&
+ ! post?.has_theme_file &&
+ post.permissions?.update
+ );
+ }
+
+ return post.type === PATTERN_TYPES.user && post.permissions?.update;
+ },
+ RenderModal: ( { items, closeModal, onActionPerformed } ) => {
+ const [ item ] = items;
+ const [ title, setTitle ] = useState( () => getItemTitle( item ) );
+ const { editEntityRecord, saveEditedEntityRecord } =
+ useDispatch( coreStore );
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+
+ async function onRename( event: React.FormEvent ) {
+ event.preventDefault();
+ try {
+ await editEntityRecord( 'postType', item.type, item.id, {
+ title,
+ } );
+ // Update state before saving rerenders the list.
+ setTitle( '' );
+ closeModal?.();
+ // Persist edited entity.
+ await saveEditedEntityRecord( 'postType', item.type, item.id, {
+ throwOnError: true,
+ } );
+ createSuccessNotice( __( 'Name updated' ), {
+ type: 'snackbar',
+ } );
+ onActionPerformed?.( items );
+ } catch ( error ) {
+ const typedError = error as CoreDataError;
+ const errorMessage =
+ typedError.message && typedError.code !== 'unknown_error'
+ ? typedError.message
+ : __( 'An error occurred while updating the name' );
+ createErrorNotice( errorMessage, { type: 'snackbar' } );
+ }
+ }
+
+ return (
+
+ );
+ },
+};
+
+export default renamePost;
diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts
index 56c8c9f54c8507..7da1f71728365b 100644
--- a/packages/editor/src/dataviews/actions/utils.ts
+++ b/packages/editor/src/dataviews/actions/utils.ts
@@ -12,11 +12,19 @@ import {
TEMPLATE_POST_TYPE,
} from '../../store/constants';
-import type { Post, TemplateOrTemplatePart } from '../types';
+import type { Post, TemplatePart, Template } from '../types';
+
+export function isTemplate( post: Post ): post is Template {
+ return post.type === TEMPLATE_POST_TYPE;
+}
+
+export function isTemplatePart( post: Post ): post is TemplatePart {
+ return post.type === TEMPLATE_PART_POST_TYPE;
+}
export function isTemplateOrTemplatePart(
p: Post
-): p is TemplateOrTemplatePart {
+): p is Template | TemplatePart {
return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE;
}
@@ -39,7 +47,7 @@ export function getItemTitle( item: Post ) {
* @param template The template entity to check.
* @return Whether the template is removable.
*/
-export function isTemplateRemovable( template: TemplateOrTemplatePart ) {
+export function isTemplateRemovable( template: Template | TemplatePart ) {
if ( ! template ) {
return false;
}
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index d5e12e298039a1..a8b1573a528b50 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -13,8 +13,9 @@ import exportPattern from '../actions/export-pattern';
import resetPost from '../actions/reset-post';
import trashPost from '../actions/trash-post';
import permanentlyDeletePost from '../actions/permanently-delete-post';
-import restorePost from '../actions/restore-post';
+import renamePost from '../actions/rename-post';
import reorderPage from '../actions/reorder-page';
+import restorePost from '../actions/restore-post';
import type { PostType } from '../types';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
@@ -74,6 +75,7 @@ export const registerPostTypeActions =
.getPostType( postType ) ) as PostType;
const actions = [
+ postTypeConfig.supports?.title ? renamePost : undefined,
postTypeConfig?.supports?.[ 'page-attributes' ]
? reorderPage
: undefined,
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
index 0c31bef195eb9b..80b6f3c5ceb852 100644
--- a/packages/editor/src/dataviews/types.ts
+++ b/packages/editor/src/dataviews/types.ts
@@ -13,8 +13,17 @@ export interface BasePost {
type: string;
id: string | number;
}
-export interface TemplateOrTemplatePart extends BasePost {
- type: 'wp_template' | 'wp_template_part';
+
+export interface Template extends BasePost {
+ type: 'wp_template';
+ is_custom: boolean;
+ source: string;
+ has_theme_file: boolean;
+ id: string;
+}
+
+export interface TemplatePart extends BasePost {
+ type: 'wp_template_part';
source: string;
has_theme_file: boolean;
id: string;
@@ -31,7 +40,7 @@ export interface PostWithPageAttributesSupport extends BasePost {
menu_order: number;
}
-export type Post = TemplateOrTemplatePart | Pattern | BasePost;
+export type Post = Template | TemplatePart | Pattern | BasePost;
export type PostWithPermissions = Post & {
permissions: {
@@ -44,6 +53,7 @@ export interface PostType {
slug: string;
supports?: {
'page-attributes'?: boolean;
+ title?: boolean;
};
}
From 5d408ce8849e6183cb39f48b071400b04a1cde78 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Thu, 8 Aug 2024 16:19:58 +0200
Subject: [PATCH 005/126] DataViews Extensibility: Allow unregistering the
duplicate pattern action (#64373)
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
---
.../src/components/post-actions/actions.js | 27 -------------
.../dataviews/actions/duplicate-pattern.tsx | 40 +++++++++++++++++++
.../src/dataviews/store/private-actions.ts | 11 +++++
3 files changed, 51 insertions(+), 27 deletions(-)
create mode 100644 packages/editor/src/dataviews/actions/duplicate-pattern.tsx
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 490bab2cc2fa4b..fcad5b97a13002 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -9,7 +9,6 @@ import { store as coreStore } from '@wordpress/core-data';
import { __, sprintf, _x } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { useMemo, useState, useEffect } from '@wordpress/element';
-import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
import { parse } from '@wordpress/blocks';
import { DataForm } from '@wordpress/dataviews';
import {
@@ -31,10 +30,6 @@ import { unlock } from '../../lock-unlock';
import { CreateTemplatePartModalContents } from '../create-template-part-modal';
import { getItemTitle } from '../../dataviews/actions/utils';
-// Patterns.
-const { CreatePatternModalContents, useDuplicatePatternProps } =
- unlock( patternsPrivateApis );
-
// TODO: this should be shared with other components (see post-fields in edit-site).
const fields = [
{
@@ -268,27 +263,6 @@ const useDuplicatePostAction = ( postType ) => {
);
};
-export const duplicatePatternAction = {
- id: 'duplicate-pattern',
- label: _x( 'Duplicate', 'action label' ),
- isEligible: ( item ) => item.type !== TEMPLATE_PART_POST_TYPE,
- modalHeader: _x( 'Duplicate pattern', 'action label' ),
- RenderModal: ( { items, closeModal } ) => {
- const [ item ] = items;
- const duplicatedProps = useDuplicatePatternProps( {
- pattern: item,
- onSuccess: () => closeModal(),
- } );
- return (
-
- );
- },
-};
-
export const duplicateTemplatePartAction = {
id: 'duplicate-template-part',
label: _x( 'Duplicate', 'action label' ),
@@ -383,7 +357,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
isTemplateOrTemplatePart &&
userCanCreatePostType &&
duplicateTemplatePartAction,
- isPattern && userCanCreatePostType && duplicatePatternAction,
...defaultActions,
].filter( Boolean );
// Filter actions based on provided context. If not provided
diff --git a/packages/editor/src/dataviews/actions/duplicate-pattern.tsx b/packages/editor/src/dataviews/actions/duplicate-pattern.tsx
new file mode 100644
index 00000000000000..98f43a27c3628c
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/duplicate-pattern.tsx
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies
+ */
+import { _x } from '@wordpress/i18n';
+// @ts-ignore
+import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
+import type { Action } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import type { Pattern } from '../types';
+
+// Patterns.
+const { CreatePatternModalContents, useDuplicatePatternProps } =
+ unlock( patternsPrivateApis );
+
+const duplicatePattern: Action< Pattern > = {
+ id: 'duplicate-pattern',
+ label: _x( 'Duplicate', 'action label' ),
+ isEligible: ( item ) => item.type !== 'wp_template_part',
+ modalHeader: _x( 'Duplicate pattern', 'action label' ),
+ RenderModal: ( { items, closeModal } ) => {
+ const [ item ] = items;
+ const duplicatedProps = useDuplicatePatternProps( {
+ pattern: item,
+ onSuccess: () => closeModal?.(),
+ } );
+ return (
+
+ );
+ },
+};
+
+export default duplicatePattern;
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index a8b1573a528b50..6854c29bb0c4e4 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -9,6 +9,7 @@ import { doAction } from '@wordpress/hooks';
* Internal dependencies
*/
import deletePost from '../actions/delete-post';
+import duplicatePattern from '../actions/duplicate-pattern';
import exportPattern from '../actions/export-pattern';
import resetPost from '../actions/reset-post';
import trashPost from '../actions/trash-post';
@@ -74,7 +75,17 @@ export const registerPostTypeActions =
.resolveSelect( coreStore )
.getPostType( postType ) ) as PostType;
+ const canCreate = await registry
+ .resolveSelect( coreStore )
+ .canUser( 'create', {
+ kind: 'postType',
+ name: postType,
+ } );
+
const actions = [
+ canCreate && postTypeConfig.slug === 'wp_block'
+ ? duplicatePattern
+ : undefined,
postTypeConfig.supports?.title ? renamePost : undefined,
postTypeConfig?.supports?.[ 'page-attributes' ]
? reorderPage
From c48075b6665ec3910d00677088672c1ba9e24916 Mon Sep 17 00:00:00 2001
From: James Koster
Date: Thu, 8 Aug 2024 16:21:26 +0100
Subject: [PATCH 006/126] Reduce gap between steps in SpacingSizesControl, add
animation, remove first/last marks (#63803)
Co-authored-by: jameskoster
Co-authored-by: stokesman
Co-authored-by: richtabor
Co-authored-by: paaljoachim
Co-authored-by: bgardner
---
.../input-controls/spacing-input-control.js | 10 +++++----
.../spacing-sizes-control/style.scss | 22 ++++++++++++++-----
2 files changed, 22 insertions(+), 10 deletions(-)
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 4faf05ba254089..5cdfe47c452b23 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
@@ -188,10 +188,12 @@ export default function SpacingInputControl( {
name: size.name,
} ) );
- const marks = spacingSizes.map( ( _newValue, index ) => ( {
- value: index,
- label: undefined,
- } ) );
+ const marks = spacingSizes
+ .slice( 1, spacingSizes.length - 1 )
+ .map( ( _newValue, index ) => ( {
+ value: index + 1,
+ label: undefined,
+ } ) );
const sideLabel =
ALL_SIDES.includes( side ) && showSideInLabel ? LABELS[ side ] : '';
diff --git a/packages/block-editor/src/components/spacing-sizes-control/style.scss b/packages/block-editor/src/components/spacing-sizes-control/style.scss
index 2fb33b5c565fba..69f30a8c7d1e90 100644
--- a/packages/block-editor/src/components/spacing-sizes-control/style.scss
+++ b/packages/block-editor/src/components/spacing-sizes-control/style.scss
@@ -4,6 +4,18 @@
margin-bottom: 0;
}
+ .is-marked {
+ .components-range-control__track {
+ transition: width ease 0.1s;
+ @include reduce-motion("transition");
+ }
+
+ .components-range-control__thumb-wrapper {
+ transition: left ease 0.1s;
+ @include reduce-motion("transition");
+ }
+ }
+
.spacing-sizes-control__range-control,
.spacing-sizes-control__custom-value-range {
height: 40px;
@@ -20,18 +32,16 @@
}
.components-range-control__mark {
+ transform: translateX(-50%);
height: $grid-unit-05;
- width: 3px;
- background-color: #fff;
+ width: math.div($grid-unit-05, 2);
+ background-color: $white;
z-index: 1;
+ top: -#{$grid-unit-05};
}
.components-range-control__marks {
margin-top: 17px;
-
- :first-child {
- display: none;
- }
}
.components-range-control__thumb-wrapper {
From 88127e20ff7386ae75417ee4c6f5e19b11d542ca Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Thu, 8 Aug 2024 20:30:54 +0400
Subject: [PATCH 007/126] Block Editor: Don't hide the toolbar for an empty
default block in HTML mode (#64374)
Unlinked contributors: designsimply.
Co-authored-by: Mamaduka
Co-authored-by: stokesman
Co-authored-by: Thelmachido
Co-authored-by: mboynes
---
.../src/components/block-tools/use-show-block-tools.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/block-editor/src/components/block-tools/use-show-block-tools.js b/packages/block-editor/src/components/block-tools/use-show-block-tools.js
index 33807445b8da74..07e0ebd16a64b0 100644
--- a/packages/block-editor/src/components/block-tools/use-show-block-tools.js
+++ b/packages/block-editor/src/components/block-tools/use-show-block-tools.js
@@ -20,6 +20,7 @@ export function useShowBlockTools() {
getSelectedBlockClientId,
getFirstMultiSelectedBlockClientId,
getBlock,
+ getBlockMode,
getSettings,
hasMultiSelection,
__unstableGetEditorMode,
@@ -33,7 +34,9 @@ export function useShowBlockTools() {
const editorMode = __unstableGetEditorMode();
const hasSelectedBlock = !! clientId && !! block;
const isEmptyDefaultBlock =
- hasSelectedBlock && isUnmodifiedDefaultBlock( block );
+ hasSelectedBlock &&
+ isUnmodifiedDefaultBlock( block ) &&
+ getBlockMode( clientId ) !== 'html';
const _showEmptyBlockSideInserter =
clientId &&
! isTyping() &&
From c495c7d21e3d0c3a6d3981a332202a0dd5d11ba5 Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Thu, 8 Aug 2024 20:34:09 +0400
Subject: [PATCH 008/126] Upgrade Playwright to v1.46 (#64372)
Co-authored-by: Mamaduka
Co-authored-by: swissspidy
---
package-lock.json | 48 +++++++++++++++++------------------
package.json | 2 +-
packages/scripts/package.json | 2 +-
3 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index b0eccc961afb78..66c32076d3b364 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -99,7 +99,7 @@
"@octokit/rest": "16.26.0",
"@octokit/types": "6.34.0",
"@octokit/webhooks-types": "5.8.0",
- "@playwright/test": "1.45.1",
+ "@playwright/test": "1.46.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@react-native/babel-preset": "0.73.10",
"@react-native/metro-babel-transformer": "0.73.10",
@@ -6950,12 +6950,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz",
+ "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==",
"dev": true,
"dependencies": {
- "playwright": "1.45.1"
+ "playwright": "1.46.0"
},
"bin": {
"playwright": "cli.js"
@@ -41083,12 +41083,12 @@
"dev": true
},
"node_modules/playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz",
+ "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==",
"dev": true,
"dependencies": {
- "playwright-core": "1.45.1"
+ "playwright-core": "1.46.0"
},
"bin": {
"playwright": "cli.js"
@@ -41101,9 +41101,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz",
+ "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -54585,7 +54585,7 @@
"npm": ">=8.19.2"
},
"peerDependencies": {
- "@playwright/test": "^1.45.1",
+ "@playwright/test": "^1.46.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
@@ -60001,12 +60001,12 @@
}
},
"@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz",
+ "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==",
"dev": true,
"requires": {
- "playwright": "1.45.1"
+ "playwright": "1.46.0"
}
},
"@pmmmwh/react-refresh-webpack-plugin": {
@@ -87337,19 +87337,19 @@
"dev": true
},
"playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz",
+ "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==",
"dev": true,
"requires": {
"fsevents": "2.3.2",
- "playwright-core": "1.45.1"
+ "playwright-core": "1.46.0"
}
},
"playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz",
+ "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==",
"dev": true
},
"please-upgrade-node": {
diff --git a/package.json b/package.json
index fac57093a852c9..e4466eb4f470e1 100644
--- a/package.json
+++ b/package.json
@@ -111,7 +111,7 @@
"@octokit/rest": "16.26.0",
"@octokit/types": "6.34.0",
"@octokit/webhooks-types": "5.8.0",
- "@playwright/test": "1.45.1",
+ "@playwright/test": "1.46.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@react-native/babel-preset": "0.73.10",
"@react-native/metro-babel-transformer": "0.73.10",
diff --git a/packages/scripts/package.json b/packages/scripts/package.json
index 675d6aa9feb6d5..ff8e26b0e00c12 100644
--- a/packages/scripts/package.json
+++ b/packages/scripts/package.json
@@ -93,7 +93,7 @@
"webpack-dev-server": "^4.15.1"
},
"peerDependencies": {
- "@playwright/test": "^1.45.1",
+ "@playwright/test": "^1.46.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
From 025125bc3734a46b2a6423f7e4287355032c2e2b Mon Sep 17 00:00:00 2001
From: Ramon
Date: Fri, 9 Aug 2024 10:48:56 +1000
Subject: [PATCH 009/126] Background image: ensure consistency with defaults
and fix reset/remove functionality (#64328)
* Fixes miscellaneous bugs in the background image default values and controls.
- checks global styles for uploaded images and applies defaults
- changes the default position from "center" to "50% 50%" so it displays in the controls
- ensures that reset/remove functionality closes the panel and removes image
- do not save already-inherited styles to the block, and don't apply defaults where an inherited style already exists.
* Reduce vertical height a smidge
* Ensure defaults are accurately displayed in the background control panel.
Add defaults for position '50% 50%' for blocks so it can be displayed in the background panel controls
Pass inherited values to setBackgroundStyleDefaults so as not apply/overwrite inherited styles.
Tighten up logic in setBackgroundStyleDefaults so that background position isn't set when an inherited value is contain and the block style is something else.
* Simplify default logic for blocks with uploaded images
* Update tests.
* Adds tests for setBackgroundStyleDefaults
* Simplify the inheritence checks. For uploaded images in global styles and block supports, always apply the defaults.
* Simplify the inheritence checks. For uploaded images, always apply the defaults.
* Changelog
* Revert background.php change and comment in background-panel.js
Co-authored-by: ramonjd
Co-authored-by: andrewserong
---
backport-changelog/6.7/7137.md | 1 +
lib/block-supports/background.php | 2 +-
lib/class-wp-theme-json-gutenberg.php | 2 +-
.../global-styles/background-panel.js | 74 +++++++++++++------
.../test/use-global-styles-output.js | 2 +-
packages/block-editor/src/hooks/background.js | 39 +++++-----
.../block-editor/src/hooks/test/background.js | 60 +++++++++++++++
.../components/global-styles/screen-block.js | 3 +-
phpunit/block-supports/background-test.php | 24 +++---
phpunit/class-wp-theme-json-test.php | 2 +-
10 files changed, 151 insertions(+), 58 deletions(-)
create mode 100644 packages/block-editor/src/hooks/test/background.js
diff --git a/backport-changelog/6.7/7137.md b/backport-changelog/6.7/7137.md
index 834cb29a21e6d9..1eba52ebaf5087 100644
--- a/backport-changelog/6.7/7137.md
+++ b/backport-changelog/6.7/7137.md
@@ -1,3 +1,4 @@
https://github.com/WordPress/wordpress-develop/pull/7137
* https://github.com/WordPress/gutenberg/pull/64192
+* https://github.com/WordPress/gutenberg/pull/64328
diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php
index 811608127f47ed..a1d99133c1fc09 100644
--- a/lib/block-supports/background.php
+++ b/lib/block-supports/background.php
@@ -62,7 +62,7 @@ function gutenberg_render_background_support( $block_content, $block ) {
$background_styles['backgroundSize'] = $background_styles['backgroundSize'] ?? 'cover';
// If the background size is set to `contain` and no position is set, set the position to `center`.
if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) {
- $background_styles['backgroundPosition'] = 'center';
+ $background_styles['backgroundPosition'] = '50% 50%';
}
}
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index ad8722091c2d48..7558438dcbe745 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -2391,7 +2391,7 @@ protected static function compute_style_properties( $styles, $settings = array()
$styles['background']['backgroundSize'] = $styles['background']['backgroundSize'] ?? 'cover';
// If the background size is set to `contain` and no position is set, set the position to `center`.
if ( 'contain' === $styles['background']['backgroundSize'] && empty( $styles['background']['backgroundPosition'] ) ) {
- $styles['background']['backgroundPosition'] = 'center';
+ $styles['background']['backgroundPosition'] = '50% 50%';
}
}
$background_styles = gutenberg_style_engine_get_styles( array( 'background' => $styles['background'] ) );
diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js
index 61f572f970b762..906202206d1b76 100644
--- a/packages/block-editor/src/components/global-styles/background-panel.js
+++ b/packages/block-editor/src/components/global-styles/background-panel.js
@@ -268,8 +268,10 @@ function BackgroundImageControls( {
style,
inheritedValue,
onRemoveImage = noop,
+ onResetImage = noop,
displayInPanel,
themeFileURIs,
+ defaultValues,
} ) {
const mediaUpload = useSelect(
( select ) => select( blockEditorStore ).getSettings().mediaUpload,
@@ -319,12 +321,8 @@ function BackgroundImageControls( {
}
const sizeValue =
- style?.background?.backgroundSize ||
- inheritedValue?.background?.backgroundSize;
- const positionValue =
- style?.background?.backgroundPosition ||
- inheritedValue?.background?.backgroundPosition;
-
+ style?.background?.backgroundSize || defaultValues?.backgroundSize;
+ const positionValue = style?.background?.backgroundPosition;
onChange(
setImmutably( style, [ 'background' ], {
...style?.background,
@@ -335,6 +333,12 @@ function BackgroundImageControls( {
title: media.title || undefined,
},
backgroundPosition:
+ /*
+ * A background image uploaded and set in the editor receives a default background position of '50% 0',
+ * when the background image size is the equivalent of "Tile".
+ * This is to increase the chance that the image's focus point is visible.
+ * This is in-editor only to assist with the user experience.
+ */
! positionValue && ( 'auto' === sizeValue || ! sizeValue )
? '50% 0'
: positionValue,
@@ -372,7 +376,9 @@ function BackgroundImageControls( {
const onRemove = () =>
onChange(
- setImmutably( style, [ 'background', 'backgroundImage' ], 'none' )
+ setImmutably( style, [ 'background' ], {
+ backgroundImage: 'none',
+ } )
);
const canRemove = ! hasValue && hasBackgroundImageValue( inheritedValue );
const imgLabel =
@@ -413,6 +419,7 @@ function BackgroundImageControls( {
onClick={ () => {
closeAndFocus();
onRemove();
+ onRemoveImage();
} }
>
{ __( 'Remove' ) }
@@ -422,7 +429,7 @@ function BackgroundImageControls( {
{
closeAndFocus();
- onRemoveImage();
+ onResetImage();
} }
>
{ __( 'Reset ' ) }
@@ -453,9 +460,7 @@ function BackgroundSizeControls( {
const imageValue =
style?.background?.backgroundImage?.url ||
inheritedValue?.background?.backgroundImage?.url;
- const isUploadedImage =
- style?.background?.backgroundImage?.id ||
- inheritedValue?.background?.backgroundImage?.id;
+ const isUploadedImage = style?.background?.backgroundImage?.id;
const positionValue =
style?.background?.backgroundPosition ||
inheritedValue?.background?.backgroundPosition;
@@ -469,11 +474,19 @@ function BackgroundSizeControls( {
* Block-level controls may have different defaults to root-level controls.
* A falsy value is treated by default as `auto` (Tile).
*/
- const currentValueForToggle =
+ let currentValueForToggle =
! sizeValue && isUploadedImage
? defaultValues?.backgroundSize
: sizeValue || 'auto';
-
+ /*
+ * The incoming value could be a value + unit, e.g. '20px'.
+ * In this case set the value to 'tile'.
+ */
+ currentValueForToggle = ! [ 'cover', 'contain', 'auto' ].includes(
+ currentValueForToggle
+ )
+ ? 'auto'
+ : currentValueForToggle;
/*
* If the current value is `cover` and the repeat value is `undefined`, then
* the toggle should be unchecked as the default state. Otherwise, the toggle
@@ -510,6 +523,7 @@ function BackgroundSizeControls( {
* receives a default background position of '50% 0',
* when the toggle switches to "Tile". This is to increase the chance that
* the image's focus point is visible.
+ * This is in-editor only to assist with the user experience.
*/
if ( !! style?.background?.backgroundImage?.id ) {
nextPosition = '50% 0';
@@ -562,14 +576,20 @@ function BackgroundSizeControls( {
)
);
+ // Set a default background position for non-site-wide, uploaded images with a size of 'contain'.
+ const backgroundPositionValue =
+ ! positionValue && isUploadedImage && 'contain' === sizeValue
+ ? defaultValues?.backgroundPosition
+ : positionValue;
+
return (
-
+
hasImageValue }
+ hasValue={ () => !! value?.background }
label={ __( 'Image' ) }
onDeselect={ resetBackground }
isShownByDefault={ defaultControls.backgroundImage }
@@ -749,10 +771,14 @@ export default function BackgroundPanel( {
inheritedValue={ inheritedValue }
themeFileURIs={ themeFileURIs }
displayInPanel
- onRemoveImage={ () => {
+ onResetImage={ () => {
setIsDropDownOpen( false );
resetBackground();
} }
+ onRemoveImage={ () =>
+ setIsDropDownOpen( false )
+ }
+ defaultValues={ defaultValues }
/>
{
+ setIsDropDownOpen( false );
+ resetBackground();
+ } }
+ onRemoveImage={ () => setIsDropDownOpen( false ) }
/>
) }
diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
index f648e1db845b87..e2530fdb85f812 100644
--- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
+++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
@@ -1061,7 +1061,7 @@ describe( 'global styles renderer', () => {
)
).toEqual( [
"background-image: url( 'https://wordpress.org/assets/image.jpg' )",
- 'background-position: center',
+ 'background-position: 50% 50%',
'background-size: contain',
] );
} );
diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js
index cd0b017831b795..0d38068cdefeea 100644
--- a/packages/block-editor/src/hooks/background.js
+++ b/packages/block-editor/src/hooks/background.js
@@ -23,9 +23,10 @@ import {
export const BACKGROUND_SUPPORT_KEY = 'background';
-// Initial control values where no block style is set.
-const BACKGROUND_DEFAULT_VALUES = {
+// Initial control values.
+export const BACKGROUND_BLOCK_DEFAULT_VALUES = {
backgroundSize: 'cover',
+ backgroundPosition: '50% 50%', // used only when backgroundSize is 'contain'.
};
/**
@@ -55,31 +56,28 @@ export function hasBackgroundSupport( blockName, feature = 'any' ) {
}
export function setBackgroundStyleDefaults( backgroundStyle ) {
- if ( ! backgroundStyle ) {
+ if ( ! backgroundStyle || ! backgroundStyle?.backgroundImage?.url ) {
return;
}
- const backgroundImage = backgroundStyle?.backgroundImage;
let backgroundStylesWithDefaults;
// Set block background defaults.
- if ( !! backgroundImage?.url ) {
- if ( ! backgroundStyle?.backgroundSize ) {
- backgroundStylesWithDefaults = {
- backgroundSize: 'cover',
- };
- }
-
- if (
- 'contain' === backgroundStyle?.backgroundSize &&
- ! backgroundStyle?.backgroundPosition
- ) {
- backgroundStylesWithDefaults = {
- backgroundPosition: 'center',
- };
- }
+ if ( ! backgroundStyle?.backgroundSize ) {
+ backgroundStylesWithDefaults = {
+ backgroundSize: BACKGROUND_BLOCK_DEFAULT_VALUES.backgroundSize,
+ };
}
+ if (
+ 'contain' === backgroundStyle?.backgroundSize &&
+ ! backgroundStyle?.backgroundPosition
+ ) {
+ backgroundStylesWithDefaults = {
+ backgroundPosition:
+ BACKGROUND_BLOCK_DEFAULT_VALUES.backgroundPosition,
+ };
+ }
return backgroundStylesWithDefaults;
}
@@ -147,6 +145,7 @@ export function BackgroundImagePanel( {
style: getBlockAttributes( clientId )?.style,
_links: _settings[ globalStylesLinksDataKey ],
/*
+ * To ensure we pass down the right inherited values:
* @TODO 1. Pass inherited value down to all block style controls,
* See: packages/block-editor/src/hooks/style.js
* @TODO 2. Add support for block style variations,
@@ -187,7 +186,7 @@ export function BackgroundImagePanel( {
inheritedValue={ inheritedValue }
as={ BackgroundInspectorControl }
panelId={ clientId }
- defaultValues={ BACKGROUND_DEFAULT_VALUES }
+ defaultValues={ BACKGROUND_BLOCK_DEFAULT_VALUES }
settings={ updatedSettings }
onChange={ onChange }
value={ style }
diff --git a/packages/block-editor/src/hooks/test/background.js b/packages/block-editor/src/hooks/test/background.js
new file mode 100644
index 00000000000000..030e88ae67fbd8
--- /dev/null
+++ b/packages/block-editor/src/hooks/test/background.js
@@ -0,0 +1,60 @@
+/**
+ * Internal dependencies
+ */
+import {
+ setBackgroundStyleDefaults,
+ BACKGROUND_BLOCK_DEFAULT_VALUES,
+} from '../background';
+
+describe( 'background', () => {
+ describe( 'setBackgroundStyleDefaults', () => {
+ const backgroundStyles = {
+ backgroundImage: { id: 123, url: 'image.png' },
+ };
+ const backgroundStylesContain = {
+ backgroundImage: { id: 123, url: 'image.png' },
+ backgroundSize: 'contain',
+ };
+ const backgroundStylesNoURL = { backgroundImage: { id: 123 } };
+ it.each( [
+ [
+ 'return background size default',
+ backgroundStyles,
+ {
+ backgroundSize:
+ BACKGROUND_BLOCK_DEFAULT_VALUES.backgroundSize,
+ },
+ ],
+ [ 'return early if no styles are passed', undefined, undefined ],
+ [
+ 'return early if images has no id',
+ backgroundStylesNoURL,
+ undefined,
+ ],
+ [
+ 'return early if images has no URL',
+ backgroundStylesNoURL,
+ undefined,
+ ],
+ [
+ 'return background position default',
+ backgroundStylesContain,
+ {
+ backgroundPosition:
+ BACKGROUND_BLOCK_DEFAULT_VALUES.backgroundPosition,
+ },
+ ],
+ [
+ 'not apply background position value if one already exists in styles',
+ {
+ ...backgroundStylesContain,
+ backgroundPosition: 'center',
+ },
+ undefined,
+ ],
+ ] )( 'should %s', ( message, styles, expected ) => {
+ const result = setBackgroundStyleDefaults( styles );
+ expect( result ).toEqual( expected );
+ } );
+ } );
+} );
diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js
index a16a01956f5a88..dee921f37f1e8a 100644
--- a/packages/edit-site/src/components/global-styles/screen-block.js
+++ b/packages/edit-site/src/components/global-styles/screen-block.js
@@ -25,9 +25,10 @@ import {
VariationsPanel,
} from './variations/variations-panel';
-// Initial control values where no block style is set.
+// Initial control values.
const BACKGROUND_BLOCK_DEFAULT_VALUES = {
backgroundSize: 'cover',
+ backgroundPosition: '50% 50%', // used only when backgroundSize is 'contain'.
};
function applyFallbackStyle( border ) {
diff --git a/phpunit/block-supports/background-test.php b/phpunit/block-supports/background-test.php
index 33e72e9b070106..4c949fa82257d3 100644
--- a/phpunit/block-supports/background-test.php
+++ b/phpunit/block-supports/background-test.php
@@ -122,7 +122,7 @@ public function test_background_block_support( $theme_name, $block_name, $backgr
*/
public function data_background_block_support() {
return array(
- 'background image style is applied' => array(
+ 'background image style is applied to uploaded images' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
'block_name' => 'test/background-rules-are-output',
'background_settings' => array(
@@ -130,8 +130,8 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
+ 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
@@ -157,14 +157,14 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
+ 'id' => 123,
),
'backgroundRepeat' => 'no-repeat',
'backgroundSize' => 'contain',
'backgroundAttachment' => 'fixed',
),
- 'expected_wrapper' => 'Content
',
+ 'expected_wrapper' => 'Content
',
'wrapper' => 'Content
',
),
'background image style is appended if a style attribute already exists' => array(
@@ -175,8 +175,8 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
+ 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
@@ -190,8 +190,8 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
+ 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
@@ -205,8 +205,8 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
+ 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index b77b54ecc7d872..d366e0df44c4a2 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -4900,7 +4900,7 @@ public function test_get_block_background_image_styles() {
),
);
- $quote_styles = ":root :where(.wp-block-quote){background-image: url('http://example.org/quote.png');background-position: center;background-size: contain;}";
+ $quote_styles = ":root :where(.wp-block-quote){background-image: url('http://example.org/quote.png');background-position: 50% 50%;background-size: contain;}";
$this->assertSameCSS( $quote_styles, $theme_json->get_styles_for_block( $quote_node ), 'Styles returned from "::get_styles_for_block()" with core/quote default background styles do not match expectations' );
$verse_node = array(
From 7c079587eba227e14b09cb34a1cafeeb7b941baa Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Fri, 9 Aug 2024 10:31:12 +0900
Subject: [PATCH 010/126] Don't allow duplicating template parts in
non-block-based themes (#64379)
Co-authored-by: t-hamano
Co-authored-by: Mamaduka
Co-authored-by: MadtownLems
---
.../editor/src/components/post-actions/actions.js | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index fcad5b97a13002..425636cefede46 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -313,9 +313,15 @@ export const duplicateTemplatePartAction = {
};
export function usePostActions( { postType, onActionPerformed, context } ) {
- const { defaultActions, postTypeObject, userCanCreatePostType } = useSelect(
+ const {
+ defaultActions,
+ postTypeObject,
+ userCanCreatePostType,
+ isBlockBasedTheme,
+ } = useSelect(
( select ) => {
- const { getPostType, canUser } = select( coreStore );
+ const { getPostType, canUser, getCurrentTheme } =
+ select( coreStore );
const { getEntityActions } = unlock( select( editorStore ) );
return {
postTypeObject: getPostType( postType ),
@@ -324,10 +330,12 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
kind: 'postType',
name: postType,
} ),
+ isBlockBasedTheme: getCurrentTheme()?.is_block_theme,
};
},
[ postType ]
);
+
const { registerPostTypeActions } = unlock( useDispatch( editorStore ) );
useEffect( () => {
registerPostTypeActions( postType );
@@ -356,6 +364,7 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
: false,
isTemplateOrTemplatePart &&
userCanCreatePostType &&
+ isBlockBasedTheme &&
duplicateTemplatePartAction,
...defaultActions,
].filter( Boolean );
From b6da9e018677c06a49fc06db1e69aa4bd1460e40 Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Fri, 9 Aug 2024 10:31:53 +0900
Subject: [PATCH 011/126] Fix: Cancel button in duplicate template part modal
doesn't work (#64377)
Co-authored-by: t-hamano
Co-authored-by: ntsekouras
Co-authored-by: Mamaduka
---
packages/editor/src/components/post-actions/actions.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 425636cefede46..5e727376a92258 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -307,6 +307,7 @@ export const duplicateTemplatePartAction = {
onCreate={ onTemplatePartSuccess }
onError={ closeModal }
confirmLabel={ _x( 'Duplicate', 'action label' ) }
+ closeModal={ closeModal }
/>
);
},
From 00d28d0e3dcea96b8a1f580bc25192fd197d3567 Mon Sep 17 00:00:00 2001
From: Daniel Richards
Date: Fri, 9 Aug 2024 09:45:01 +0800
Subject: [PATCH 012/126] Grid: Prevent highlight of cells when dragging a
block if block type can't be dropped into grid (#64290)
Co-authored-by: talldan
Co-authored-by: noisysocks
Co-authored-by: hanneslsm
---
.../src/components/grid/grid-visualizer.js | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js
index e1d35f012b4d81..fad2f5cfb14835 100644
--- a/packages/block-editor/src/components/grid/grid-visualizer.js
+++ b/packages/block-editor/src/components/grid/grid-visualizer.js
@@ -206,8 +206,12 @@ function useGridVisualizerDropZone(
gridInfo,
setHighlightedRect
) {
- const { getBlockAttributes, getBlockRootClientId } =
- useSelect( blockEditorStore );
+ const {
+ getBlockAttributes,
+ getBlockRootClientId,
+ canInsertBlockType,
+ getBlockName,
+ } = useSelect( blockEditorStore );
const {
updateBlockAttributes,
moveBlocksToPosition,
@@ -221,6 +225,10 @@ function useGridVisualizerDropZone(
return useDropZoneWithValidation( {
validateDrag( srcClientId ) {
+ const blockName = getBlockName( srcClientId );
+ if ( ! canInsertBlockType( blockName, gridClientId ) ) {
+ return false;
+ }
const attributes = getBlockAttributes( srcClientId );
const rect = new GridRect( {
columnStart: column,
From b11bdc0645a0503437389c29d39ac266ba6cd2bf Mon Sep 17 00:00:00 2001
From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com>
Date: Fri, 9 Aug 2024 09:46:40 +0530
Subject: [PATCH 013/126] Add border support to preformatted block (#64302)
Co-authored-by: akasunil
Co-authored-by: ramonjd
---
packages/block-library/src/preformatted/block.json | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/packages/block-library/src/preformatted/block.json b/packages/block-library/src/preformatted/block.json
index fbec3581bc9d42..a1726ee8b0d43c 100644
--- a/packages/block-library/src/preformatted/block.json
+++ b/packages/block-library/src/preformatted/block.json
@@ -43,6 +43,18 @@
},
"interactivity": {
"clientNavigation": true
+ },
+ "__experimentalBorder": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true,
+ "__experimentalDefaultControls": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true
+ }
}
},
"style": "wp-block-preformatted"
From 0ceac8f5e8b74cbeb7ef36c44009c67ec5fabbf7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com>
Date: Fri, 9 Aug 2024 09:16:08 +0200
Subject: [PATCH 014/126] Quick edit: add Date as field and `datetime` as field
type (#64267)
Co-authored-by: oandregal
Co-authored-by: ntsekouras
Co-authored-by: youknowriad
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
Co-authored-by: jameskoster
---
packages/components/CHANGELOG.md | 4 +
.../components/src/date-time/time/index.tsx | 38 +++++---
packages/components/src/date-time/types.ts | 12 ++-
.../dataform/stories/index.story.tsx | 18 +++-
.../components/dataviews/stories/fixtures.js | 16 ++++
.../dataviews/src/field-types/datetime.tsx | 95 +++++++++++++++++++
packages/dataviews/src/field-types/index.tsx | 5 +
.../src/test/filter-and-sort-data-view.js | 28 ++++++
packages/dataviews/src/types.ts | 2 +-
.../src/components/post-edit/index.js | 5 +-
.../src/components/post-fields/index.js | 1 +
11 files changed, 206 insertions(+), 18 deletions(-)
create mode 100644 packages/dataviews/src/field-types/datetime.tsx
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index ef0108b09a741d..47afb0711090d8 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Enhancements
+
+- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
+
## 28.5.0 (2024-08-07)
### Bug Fixes
diff --git a/packages/components/src/date-time/time/index.tsx b/packages/components/src/date-time/time/index.tsx
index 5f706d69190095..90a8a901354a39 100644
--- a/packages/components/src/date-time/time/index.tsx
+++ b/packages/components/src/date-time/time/index.tsx
@@ -13,6 +13,7 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import BaseControl from '../../base-control';
+import { VisuallyHidden } from '../../visually-hidden';
import SelectControl from '../../select-control';
import TimeZone from './timezone';
import type { TimeInputValue, TimePickerProps } from '../types';
@@ -61,6 +62,7 @@ export function TimePicker( {
currentTime,
onChange,
dateOrder: dateOrderProp,
+ hideLabelFromVision = false,
}: TimePickerProps ) {
const [ date, setDate ] = useState( () =>
// Truncate the date at the minutes, see: #15495.
@@ -219,12 +221,18 @@ export function TimePicker( {
className="components-datetime__time" // Unused, for backwards compatibility.
>
-
- { __( 'Time' ) }
-
+ { hideLabelFromVision ? (
+
+ { __( 'Time' ) }
+
+ ) : (
+
+ { __( 'Time' ) }
+
+ ) }
@@ -241,12 +249,18 @@ export function TimePicker( {
-
- { __( 'Date' ) }
-
+ { hideLabelFromVision ? (
+
+ { __( 'Date' ) }
+
+ ) : (
+
+ { __( 'Date' ) }
+
+ ) }
diff --git a/packages/components/src/date-time/types.ts b/packages/components/src/date-time/types.ts
index b3716d5c135c64..1f2298d9d450b5 100644
--- a/packages/components/src/date-time/types.ts
+++ b/packages/components/src/date-time/types.ts
@@ -29,6 +29,13 @@ export type TimePickerProps = {
* time as an argument.
*/
onChange?: ( time: string ) => void;
+
+ /**
+ * If true, the label will only be visible to screen readers.
+ *
+ * @default false
+ */
+ hideLabelFromVision?: boolean;
};
export type TimeInputValue = {
@@ -130,7 +137,10 @@ export type DatePickerProps = {
};
export type DateTimePickerProps = Omit< DatePickerProps, 'onChange' > &
- Omit< TimePickerProps, 'currentTime' | 'onChange' > & {
+ Omit<
+ TimePickerProps,
+ 'currentTime' | 'onChange' | 'hideLabelFromVision'
+ > & {
/**
* The function called when a new date or time has been selected. It is
* passed the date and time as an argument.
diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx
index 4863eb24b4ede4..7808756b7b6557 100644
--- a/packages/dataviews/src/components/dataform/stories/index.story.tsx
+++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx
@@ -33,6 +33,20 @@ const fields = [
label: 'Order',
type: 'integer' as const,
},
+ {
+ id: 'date',
+ label: 'Date',
+ type: 'datetime' as const,
+ },
+ {
+ id: 'birthdate',
+ label: 'Date as options',
+ type: 'datetime' as const,
+ elements: [
+ { value: '1970-02-23T12:00:00', label: "Jane's birth date" },
+ { value: '1950-02-23T12:00:00', label: "John's birth date" },
+ ],
+ },
{
id: 'author',
label: 'Author',
@@ -59,10 +73,12 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => {
order: 2,
author: 1,
status: 'draft',
+ date: '2021-01-01T12:00:00',
+ birthdate: '1950-02-23T12:00:00',
} );
const form = {
- fields: [ 'title', 'order', 'author', 'status' ],
+ fields: [ 'title', 'order', 'author', 'status', 'date', 'birthdate' ],
};
return (
diff --git a/packages/dataviews/src/components/dataviews/stories/fixtures.js b/packages/dataviews/src/components/dataviews/stories/fixtures.js
index 536c5e66e6ce97..f89cea81e6f15d 100644
--- a/packages/dataviews/src/components/dataviews/stories/fixtures.js
+++ b/packages/dataviews/src/components/dataviews/stories/fixtures.js
@@ -23,6 +23,7 @@ export const data = [
type: 'Not a planet',
categories: [ 'Space', 'NASA' ],
satellites: 0,
+ date: '2021-01-01T00:00:00Z',
},
{
id: 2,
@@ -32,6 +33,7 @@ export const data = [
type: 'Not a planet',
categories: [ 'Space' ],
satellites: 0,
+ date: '2019-01-02T00:00:00Z',
},
{
id: 3,
@@ -41,6 +43,7 @@ export const data = [
type: 'Not a planet',
categories: [ 'NASA' ],
satellites: 0,
+ date: '2025-01-03T00:00:00Z',
},
{
id: 4,
@@ -50,6 +53,7 @@ export const data = [
type: 'Ice giant',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 14,
+ date: '2020-01-01T00:00:00Z',
},
{
id: 5,
@@ -59,6 +63,7 @@ export const data = [
type: 'Terrestrial',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 0,
+ date: '2020-01-02T01:00:00Z',
},
{
id: 6,
@@ -68,6 +73,7 @@ export const data = [
type: 'Terrestrial',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 0,
+ date: '2020-01-02T00:00:00Z',
},
{
id: 7,
@@ -77,6 +83,7 @@ export const data = [
type: 'Terrestrial',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 1,
+ date: '2023-01-03T00:00:00Z',
},
{
id: 8,
@@ -86,6 +93,7 @@ export const data = [
type: 'Terrestrial',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 2,
+ date: '2020-01-01T00:00:00Z',
},
{
id: 9,
@@ -95,6 +103,7 @@ export const data = [
type: 'Gas giant',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 95,
+ date: '2017-01-01T00:01:00Z',
},
{
id: 10,
@@ -104,6 +113,7 @@ export const data = [
type: 'Gas giant',
categories: [ 'Space', 'Planet', 'Solar system' ],
satellites: 146,
+ date: '2020-02-01T00:02:00Z',
},
{
id: 11,
@@ -113,6 +123,7 @@ export const data = [
type: 'Ice giant',
categories: [ 'Space', 'Ice giant', 'Solar system' ],
satellites: 28,
+ date: '2020-03-01T00:00:00Z',
},
];
@@ -175,6 +186,11 @@ export const fields = [
enableHiding: false,
enableGlobalSearch: true,
},
+ {
+ id: 'date',
+ label: 'Date',
+ type: 'datetime',
+ },
{
label: 'Type',
id: 'type',
diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx
new file mode 100644
index 00000000000000..3861a0db635d7b
--- /dev/null
+++ b/packages/dataviews/src/field-types/datetime.tsx
@@ -0,0 +1,95 @@
+/**
+ * WordPress dependencies
+ */
+import { BaseControl, TimePicker, SelectControl } from '@wordpress/components';
+import { useCallback } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import type {
+ SortDirection,
+ ValidationContext,
+ DataFormControlProps,
+} from '../types';
+
+function sort( a: any, b: any, direction: SortDirection ) {
+ const timeA = new Date( a ).getTime();
+ const timeB = new Date( b ).getTime();
+
+ return direction === 'asc' ? timeA - timeB : timeB - timeA;
+}
+
+function isValid( value: any, context?: ValidationContext ) {
+ if ( context?.elements ) {
+ const validValues = context?.elements.map( ( f ) => f.value );
+ if ( ! validValues.includes( value ) ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function Edit< Item >( {
+ data,
+ field,
+ onChange,
+}: DataFormControlProps< Item > ) {
+ const { id, label } = field;
+ const value = field.getValue( { item: data } );
+
+ const onChangeControl = useCallback(
+ ( newValue: string | null ) =>
+ onChange( ( prevItem: Item ) => ( {
+ ...prevItem,
+ [ id ]: newValue,
+ } ) ),
+ [ id, onChange ]
+ );
+
+ if ( field.elements ) {
+ const elements = [
+ /*
+ * Value can be undefined when:
+ *
+ * - the field is not required
+ * - in bulk editing
+ *
+ */
+ { label: __( 'Select item' ), value: '' },
+ ...field.elements,
+ ];
+
+ return (
+
+ );
+ }
+
+ return (
+
+
+ { label }
+
+
+
+ );
+}
+
+export default {
+ sort,
+ isValid,
+ Edit,
+};
diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx
index 3d1d824ec36bf2..eb9dada479c6bf 100644
--- a/packages/dataviews/src/field-types/index.tsx
+++ b/packages/dataviews/src/field-types/index.tsx
@@ -4,6 +4,7 @@
import type { FieldType, SortDirection, ValidationContext } from '../types';
import { default as integer } from './integer';
import { default as text } from './text';
+import { default as datetime } from './datetime';
/**
*
@@ -20,6 +21,10 @@ export default function getFieldTypeDefinition( type?: FieldType ) {
return text;
}
+ if ( 'datetime' === type ) {
+ return datetime;
+ }
+
return {
sort: ( a: any, b: any, direction: SortDirection ) => {
if ( typeof a === 'number' && typeof b === 'number' ) {
diff --git a/packages/dataviews/src/test/filter-and-sort-data-view.js b/packages/dataviews/src/test/filter-and-sort-data-view.js
index 7538b975a30992..7f0b3a30e8587c 100644
--- a/packages/dataviews/src/test/filter-and-sort-data-view.js
+++ b/packages/dataviews/src/test/filter-and-sort-data-view.js
@@ -268,6 +268,34 @@ describe( 'sorting', () => {
expect( result[ 1 ].title ).toBe( 'Neptune' );
} );
+ it( 'should sort datetime field types', () => {
+ const { data: resultDesc } = filterSortAndPaginate(
+ data,
+ {
+ sort: { field: 'date', direction: 'desc' },
+ },
+ fields
+ );
+ expect( resultDesc ).toHaveLength( 11 );
+ expect( resultDesc[ 0 ].title ).toBe( 'NASA' );
+ expect( resultDesc[ 1 ].title ).toBe( 'Earth' );
+ expect( resultDesc[ 9 ].title ).toBe( 'Space' );
+ expect( resultDesc[ 10 ].title ).toBe( 'Jupiter' );
+
+ const { data: resultAsc } = filterSortAndPaginate(
+ data,
+ {
+ sort: { field: 'date', direction: 'asc' },
+ },
+ fields
+ );
+ expect( resultAsc ).toHaveLength( 11 );
+ expect( resultAsc[ 0 ].title ).toBe( 'Jupiter' );
+ expect( resultAsc[ 1 ].title ).toBe( 'Space' );
+ expect( resultAsc[ 9 ].title ).toBe( 'Earth' );
+ expect( resultAsc[ 10 ].title ).toBe( 'NASA' );
+ } );
+
it( 'should sort untyped fields if the value is a number', () => {
const { data: result } = filterSortAndPaginate(
data,
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index b0873e9c677f53..2e87d1371acb61 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -47,7 +47,7 @@ export type Operator =
| 'isAll'
| 'isNotAll';
-export type FieldType = 'text' | 'integer';
+export type FieldType = 'text' | 'integer' | 'datetime';
export type ValidationContext = {
elements?: Option[];
diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index 86e2bf4d6edea1..03158e00862154 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -47,9 +47,9 @@ function PostEditForm( { postType, postId } ) {
const { fields } = usePostFields();
const form = {
type: 'panel',
- fields: [ 'title', 'author' ],
+ fields: [ 'title', 'author', 'date' ],
};
- const [ edits, setEdits ] = useState( {} );
+ const [ edits, setEdits ] = useState( initialEdits );
const itemWithEdits = useMemo( () => {
return {
...initialEdits,
@@ -71,7 +71,6 @@ function PostEditForm( { postType, postId } ) {
...edits,
} );
}
- setEdits( {} );
};
const isUpdateDisabled = ! isItemValid( itemWithEdits, fields, form );
diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js
index f476b676264ed8..44625fbfbfafb9 100644
--- a/packages/edit-site/src/components/post-fields/index.js
+++ b/packages/edit-site/src/components/post-fields/index.js
@@ -271,6 +271,7 @@ function usePostFields( viewType ) {
{
label: __( 'Date' ),
id: 'date',
+ type: 'datetime',
render: ( { item } ) => {
const isDraftOrPrivate = [ 'draft', 'private' ].includes(
item.status
From 97285c23d69955776b7d1b6d1468167d081beb7b Mon Sep 17 00:00:00 2001
From: Shail Mehta
Date: Fri, 9 Aug 2024 13:20:15 +0530
Subject: [PATCH 015/126] Comment Date: Added Border Support (#64210)
Co-authored-by: shail-mehta
Co-authored-by: t-hamano
Co-authored-by: aaronrobertshaw
---
.../block-library/src/comment-date/block.json | 15 ++++++++++++++-
.../block-library/src/comment-date/style.scss | 4 ++++
packages/block-library/src/style.scss | 1 +
3 files changed, 19 insertions(+), 1 deletion(-)
create mode 100644 packages/block-library/src/comment-date/style.scss
diff --git a/packages/block-library/src/comment-date/block.json b/packages/block-library/src/comment-date/block.json
index ddc0281e6c668c..1e8110f836d93b 100644
--- a/packages/block-library/src/comment-date/block.json
+++ b/packages/block-library/src/comment-date/block.json
@@ -47,6 +47,19 @@
},
"interactivity": {
"clientNavigation": true
+ },
+ "__experimentalBorder": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true,
+ "__experimentalDefaultControls": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true
+ }
}
- }
+ },
+ "style": "wp-block-comment-date"
}
diff --git a/packages/block-library/src/comment-date/style.scss b/packages/block-library/src/comment-date/style.scss
new file mode 100644
index 00000000000000..9b897defa655b7
--- /dev/null
+++ b/packages/block-library/src/comment-date/style.scss
@@ -0,0 +1,4 @@
+.wp-block-comment-date {
+ // This block has customizable padding, border-box makes that more predictable.
+ box-sizing: border-box;
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 8f17cd7a50f55c..df1337d7eb25c0 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -10,6 +10,7 @@
@import "./comments/style.scss";
@import "./comments-pagination/style.scss";
@import "./comment-template/style.scss";
+@import "./comment-date/style.scss";
@import "./cover/style.scss";
@import "./details/style.scss";
@import "./embed/style.scss";
From 651cc457521a6ec81367362618433095ae611fb0 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Fri, 9 Aug 2024 10:49:31 +0200
Subject: [PATCH 016/126] DataViews Quick Edit: Add Post Card to the quick edit
panel (#64365)
Co-authored-by: youknowriad
Co-authored-by: oandregal
Co-authored-by: jameskoster
---
.../src/components/post-edit/index.js | 7 ++++
.../src/components/post-card-panel/index.js | 42 ++++++++-----------
.../src/components/sidebar/post-summary.js | 29 ++++++++-----
packages/editor/src/private-apis.js | 2 +
4 files changed, 45 insertions(+), 35 deletions(-)
diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index 03158e00862154..35141f743cd2e3 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -16,12 +16,16 @@ import {
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useState, useMemo } from '@wordpress/element';
+import { privateApis as editorPrivateApis } from '@wordpress/editor';
/**
* Internal dependencies
*/
import Page from '../page';
import usePostFields from '../post-fields';
+import { unlock } from '../../lock-unlock';
+
+const { PostCardPanel } = unlock( editorPrivateApis );
function PostEditForm( { postType, postId } ) {
const ids = useMemo( () => postId.split( ',' ), [ postId ] );
@@ -76,6 +80,9 @@ function PostEditForm( { postType, postId } ) {
const isUpdateDisabled = ! isItemValid( itemWithEdits, fields, form );
return (
+ { ids.length === 1 && (
+
+ ) }
{
- const {
- getEditedPostAttribute,
- getCurrentPostType,
- getCurrentPostId,
- __experimentalGetTemplateInfo,
- } = select( editorStore );
- const { canUser } = select( coreStore );
- const { getEditedEntityRecord } = select( coreStore );
+ const { __experimentalGetTemplateInfo } = select( editorStore );
+ const { canUser, getEditedEntityRecord } = select( coreStore );
const siteSettings = canUser( 'read', {
kind: 'root',
name: 'site',
} )
? getEditedEntityRecord( 'root', 'site' )
: undefined;
- const _type = getCurrentPostType();
- const _id = getCurrentPostId();
- const _record = getEditedEntityRecord( 'postType', _type, _id );
+ const _record = getEditedEntityRecord(
+ 'postType',
+ postType,
+ postId
+ );
const _templateInfo =
[ TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE ].includes(
- _type
+ postType
) && __experimentalGetTemplateInfo( _record );
let _isSync = false;
- if ( GLOBAL_POST_TYPES.includes( _type ) ) {
- if ( PATTERN_POST_TYPE === _type ) {
+ if ( GLOBAL_POST_TYPES.includes( postType ) ) {
+ if ( PATTERN_POST_TYPE === postType ) {
// When the post is first created, the top level wp_pattern_sync_status is not set so get meta value instead.
const currentSyncStatus =
- getEditedPostAttribute( 'meta' )
- ?.wp_pattern_sync_status === 'unsynced'
+ _record?.meta?.wp_pattern_sync_status === 'unsynced'
? 'unsynced'
- : getEditedPostAttribute(
- 'wp_pattern_sync_status'
- );
+ : _record?.wp_pattern_sync_status;
_isSync = currentSyncStatus !== 'unsynced';
} else {
_isSync = true;
}
}
return {
- title:
- _templateInfo?.title || getEditedPostAttribute( 'title' ),
- icon: unlock( select( editorStore ) ).getPostIcon( _type, {
+ title: _templateInfo?.title || _record?.title,
+ icon: unlock( select( editorStore ) ).getPostIcon( postType, {
area: _record?.area,
} ),
isSync: _isSync,
- isFrontPage: siteSettings?.page_on_front === _id,
- isPostsPage: siteSettings?.page_for_posts === _id,
+ isFrontPage: siteSettings?.page_on_front === postId,
+ isPostsPage: siteSettings?.page_for_posts === postId,
};
},
[]
diff --git a/packages/editor/src/components/sidebar/post-summary.js b/packages/editor/src/components/sidebar/post-summary.js
index a6a95d36388ba5..573a59de827188 100644
--- a/packages/editor/src/components/sidebar/post-summary.js
+++ b/packages/editor/src/components/sidebar/post-summary.js
@@ -36,16 +36,23 @@ import { PrivatePostLastRevision } from '../post-last-revision';
const PANEL_NAME = 'post-status';
export default function PostSummary( { onActionPerformed } ) {
- const { isRemovedPostStatusPanel } = useSelect( ( select ) => {
- // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do
- // not use isEditorPanelEnabled since this panel should not be disabled through the UI.
- const { isEditorPanelRemoved, getCurrentPostType } =
- select( editorStore );
- return {
- isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ),
- postType: getCurrentPostType(),
- };
- }, [] );
+ const { isRemovedPostStatusPanel, postType, postId } = useSelect(
+ ( select ) => {
+ // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do
+ // not use isEditorPanelEnabled since this panel should not be disabled through the UI.
+ const {
+ isEditorPanelRemoved,
+ getCurrentPostType,
+ getCurrentPostId,
+ } = select( editorStore );
+ return {
+ isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ),
+ postType: getCurrentPostType(),
+ postId: getCurrentPostId(),
+ };
+ },
+ []
+ );
return (
@@ -54,6 +61,8 @@ export default function PostSummary( { onActionPerformed } ) {
<>
Date: Fri, 9 Aug 2024 10:57:41 +0200
Subject: [PATCH 017/126] Add `comment_status` field to quick edit (#64370)
Co-authored-by: oandregal
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
---
.../components/dataform-controls/index.tsx | 46 +++++++++++++++++++
.../components/dataform-controls/radio.tsx | 43 +++++++++++++++++
.../dataform/stories/index.story.tsx | 22 ++++++++-
packages/dataviews/src/normalize-fields.ts | 3 +-
packages/dataviews/src/types.ts | 22 ++++++++-
.../src/components/post-edit/index.js | 2 +-
.../src/components/post-fields/index.js | 26 +++++++++++
7 files changed, 160 insertions(+), 4 deletions(-)
create mode 100644 packages/dataviews/src/components/dataform-controls/index.tsx
create mode 100644 packages/dataviews/src/components/dataform-controls/radio.tsx
diff --git a/packages/dataviews/src/components/dataform-controls/index.tsx b/packages/dataviews/src/components/dataform-controls/index.tsx
new file mode 100644
index 00000000000000..dd913269cd09ea
--- /dev/null
+++ b/packages/dataviews/src/components/dataform-controls/index.tsx
@@ -0,0 +1,46 @@
+/**
+ * External dependencies
+ */
+import type { ComponentType } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import type {
+ DataFormControlProps,
+ Field,
+ FieldTypeDefinition,
+} from '../../types';
+import radio from './radio';
+
+interface FormControls {
+ [ key: string ]: ComponentType< DataFormControlProps< any > >;
+}
+
+const FORM_CONTROLS: FormControls = {
+ radio,
+};
+
+export function getControl< Item >(
+ field: Field< Item >,
+ fieldTypeDefinition: FieldTypeDefinition< Item >
+) {
+ if ( typeof field.Edit === 'function' ) {
+ return field.Edit;
+ }
+
+ let control;
+ if ( typeof field.Edit === 'string' ) {
+ control = getControlByType( field.Edit );
+ }
+
+ return control || fieldTypeDefinition.Edit;
+}
+
+export function getControlByType( type: string ) {
+ if ( Object.keys( FORM_CONTROLS ).includes( type ) ) {
+ return FORM_CONTROLS[ type ];
+ }
+
+ return null;
+}
diff --git a/packages/dataviews/src/components/dataform-controls/radio.tsx b/packages/dataviews/src/components/dataform-controls/radio.tsx
new file mode 100644
index 00000000000000..d264aa6c24b7fb
--- /dev/null
+++ b/packages/dataviews/src/components/dataform-controls/radio.tsx
@@ -0,0 +1,43 @@
+/**
+ * WordPress dependencies
+ */
+import { RadioControl } from '@wordpress/components';
+import { useCallback } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { DataFormControlProps } from '../../types';
+
+export default function Edit< Item >( {
+ data,
+ field,
+ onChange,
+ hideLabelFromVision,
+}: DataFormControlProps< Item > ) {
+ const { id, label } = field;
+ const value = field.getValue( { item: data } );
+
+ const onChangeControl = useCallback(
+ ( newValue: string ) =>
+ onChange( ( prevItem: Item ) => ( {
+ ...prevItem,
+ [ id ]: newValue,
+ } ) ),
+ [ id, onChange ]
+ );
+
+ if ( field.elements ) {
+ return (
+
+ );
+ }
+
+ return null;
+}
diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx
index 7808756b7b6557..7f3c5ff879b72a 100644
--- a/packages/dataviews/src/components/dataform/stories/index.story.tsx
+++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx
@@ -56,6 +56,17 @@ const fields = [
{ value: 2, label: 'John' },
],
},
+ {
+ id: 'reviewer',
+ label: 'Reviewer',
+ type: 'text' as const,
+ Edit: 'radio' as const,
+ elements: [
+ { value: 'fulano', label: 'Fulano' },
+ { value: 'mengano', label: 'Mengano' },
+ { value: 'zutano', label: 'Zutano' },
+ ],
+ },
{
id: 'status',
label: 'Status',
@@ -73,12 +84,21 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => {
order: 2,
author: 1,
status: 'draft',
+ reviewer: 'fulano',
date: '2021-01-01T12:00:00',
birthdate: '1950-02-23T12:00:00',
} );
const form = {
- fields: [ 'title', 'order', 'author', 'status', 'date', 'birthdate' ],
+ fields: [
+ 'title',
+ 'order',
+ 'author',
+ 'reviewer',
+ 'status',
+ 'date',
+ 'birthdate',
+ ],
};
return (
diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts
index 680749df5344a6..54992ff22fe2ae 100644
--- a/packages/dataviews/src/normalize-fields.ts
+++ b/packages/dataviews/src/normalize-fields.ts
@@ -3,6 +3,7 @@
*/
import getFieldTypeDefinition from './field-types';
import type { Field, NormalizedField } from './types';
+import { getControl } from './components/dataform-controls';
/**
* Apply default values and normalize the fields config.
@@ -38,7 +39,7 @@ export function normalizeFields< Item >(
);
};
- const Edit = field.Edit || fieldTypeDefinition.Edit;
+ const Edit = getControl( field, fieldTypeDefinition );
const renderFromElements = ( { item }: { item: Item } ) => {
const value = getValue( { item } );
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 2e87d1371acb61..d8a5ee8f68ecef 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -53,6 +53,26 @@ export type ValidationContext = {
elements?: Option[];
};
+/**
+ * An abstract interface for Field based on the field type.
+ */
+export type FieldTypeDefinition< Item > = {
+ /**
+ * Callback used to sort the field.
+ */
+ sort: ( a: Item, b: Item, direction: SortDirection ) => number;
+
+ /**
+ * Callback used to validate the field.
+ */
+ isValid: ( item: Item, context?: ValidationContext ) => boolean;
+
+ /**
+ * Callback used to render an edit control for the field.
+ */
+ Edit: ComponentType< DataFormControlProps< Item > >;
+};
+
/**
* A dataview field for a specific property of a data type.
*/
@@ -90,7 +110,7 @@ export type Field< Item > = {
/**
* Callback used to render an edit control for the field.
*/
- Edit?: ComponentType< DataFormControlProps< Item > >;
+ Edit?: ComponentType< DataFormControlProps< Item > > | 'radio';
/**
* Callback used to sort the field.
diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index 35141f743cd2e3..0a56fdfe5786b7 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -51,7 +51,7 @@ function PostEditForm( { postType, postId } ) {
const { fields } = usePostFields();
const form = {
type: 'panel',
- fields: [ 'title', 'author', 'date' ],
+ fields: [ 'title', 'author', 'date', 'comment_status' ],
};
const [ edits, setEdits ] = useState( initialEdits );
const itemWithEdits = useMemo( () => {
diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js
index 44625fbfbfafb9..b03b2c6f5be3c4 100644
--- a/packages/edit-site/src/components/post-fields/index.js
+++ b/packages/edit-site/src/components/post-fields/index.js
@@ -345,6 +345,32 @@ function usePostFields( viewType ) {
return { getFormattedDate( item.date ) } ;
},
},
+ {
+ id: 'comment_status',
+ label: __( 'Discussion' ),
+ type: 'text',
+ Edit: 'radio',
+ enableSorting: false,
+ filterBy: {
+ operators: [],
+ },
+ elements: [
+ {
+ value: 'open',
+ label: __( 'Open' ),
+ description: __(
+ 'Visitors can add new comments and replies.'
+ ),
+ },
+ {
+ value: 'closed',
+ label: __( 'Closed' ),
+ description: __(
+ 'Visitors cannot add new comments or replies. Existing comments remain visible.'
+ ),
+ },
+ ],
+ },
],
[ authors, viewType, frontPageId, postsPageId ]
);
From 360c6f9b2be933baa0c224ce7a2d4f48593bee0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com>
Date: Fri, 9 Aug 2024 11:42:34 +0200
Subject: [PATCH 018/126] DataViews: update `renderFormElements` to make sure
the value respects the type (#64391)
Co-authored-by: oandregal
Co-authored-by: youknowriad
---
packages/dataviews/src/field-types/integer.tsx | 2 +-
packages/dataviews/src/normalize-fields.ts | 11 ++++-------
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx
index 1233c3ab555567..bd4956493232df 100644
--- a/packages/dataviews/src/field-types/integer.tsx
+++ b/packages/dataviews/src/field-types/integer.tsx
@@ -53,7 +53,7 @@ function Edit< Item >( {
( newValue: string | undefined ) =>
onChange( ( prevItem: Item ) => ( {
...prevItem,
- [ id ]: newValue,
+ [ id ]: Number( newValue ),
} ) ),
[ id, onChange ]
);
diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts
index 54992ff22fe2ae..2cdde5b3343798 100644
--- a/packages/dataviews/src/normalize-fields.ts
+++ b/packages/dataviews/src/normalize-fields.ts
@@ -43,13 +43,10 @@ export function normalizeFields< Item >(
const renderFromElements = ( { item }: { item: Item } ) => {
const value = getValue( { item } );
- const label = field?.elements?.find( ( element ) => {
- // Intentionally using == here to allow for type coercion.
- // eslint-disable-next-line eqeqeq
- return element.value == value;
- } )?.label;
-
- return label || value;
+ return (
+ field?.elements?.find( ( element ) => element.value === value )
+ ?.label || getValue( { item } )
+ );
};
const render =
From 758915a0e4346ca51a9e3fda5bee4500ff4e3976 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Fri, 9 Aug 2024 11:49:04 +0200
Subject: [PATCH 019/126] DataViews Quick Edit: Fix panel title not updating
---
packages/editor/src/components/post-card-panel/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/editor/src/components/post-card-panel/index.js b/packages/editor/src/components/post-card-panel/index.js
index 5e20ecbcf07df9..5ac88b3dbc7f94 100644
--- a/packages/editor/src/components/post-card-panel/index.js
+++ b/packages/editor/src/components/post-card-panel/index.js
@@ -70,7 +70,7 @@ export default function PostCardPanel( { postType, postId, actions } ) {
isPostsPage: siteSettings?.page_for_posts === postId,
};
},
- []
+ [ postId, postType ]
);
return (
From f5959ccdbab3fab3f95525c2d711e419cffe7d0a Mon Sep 17 00:00:00 2001
From: Amit Raj <77401999+amitraj2203@users.noreply.github.com>
Date: Fri, 9 Aug 2024 15:39:42 +0530
Subject: [PATCH 020/126] Latests Posts: Used ToggleGroupControl instead for
Image alignment (#64352)
* Used ToggleGroupControl instead of BlockAlignmentToolbar
* Addressed feedback
* Use position icons instead of align
* Remove isBlock prop from ToggleGroupControl
Co-authored-by: amitraj2203
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: t-hamano
---
.../block-library/src/latest-posts/edit.js | 76 ++++++++++++++-----
.../src/latest-posts/editor.scss | 6 --
2 files changed, 59 insertions(+), 23 deletions(-)
diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js
index 49a24b08f68d77..533e9621fd4f2b 100644
--- a/packages/block-library/src/latest-posts/edit.js
+++ b/packages/block-library/src/latest-posts/edit.js
@@ -7,7 +7,6 @@ import clsx from 'clsx';
* WordPress dependencies
*/
import {
- BaseControl,
PanelBody,
Placeholder,
QueryControls,
@@ -16,19 +15,28 @@ import {
Spinner,
ToggleControl,
ToolbarGroup,
+ __experimentalToggleGroupControl as ToggleGroupControl,
+ __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
} from '@wordpress/components';
import { __, _x, sprintf } from '@wordpress/i18n';
import { dateI18n, format, getSettings } from '@wordpress/date';
import {
InspectorControls,
- BlockAlignmentToolbar,
BlockControls,
__experimentalImageSizeControl as ImageSizeControl,
useBlockProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
-import { pin, list, grid } from '@wordpress/icons';
+import {
+ pin,
+ list,
+ grid,
+ alignNone,
+ positionLeft,
+ positionCenter,
+ positionRight,
+} from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticeStore } from '@wordpress/notices';
import { useInstanceId } from '@wordpress/compose';
@@ -197,6 +205,29 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) {
setAttributes( { categories: allCategories } );
};
+ const imageAlignmentOptions = [
+ {
+ value: 'none',
+ icon: alignNone,
+ label: __( 'None' ),
+ },
+ {
+ value: 'left',
+ icon: positionLeft,
+ label: __( 'Left' ),
+ },
+ {
+ value: 'center',
+ icon: positionCenter,
+ label: __( 'Center' ),
+ },
+ {
+ value: 'right',
+ icon: positionRight,
+ label: __( 'Right' ),
+ },
+ ];
+
const hasPosts = !! latestPosts?.length;
const inspectorControls = (
@@ -303,21 +334,32 @@ export default function LatestPostsEdit( { attributes, setAttributes } ) {
} )
}
/>
-
-
- { __( 'Image alignment' ) }
-
-
- setAttributes( {
- featuredImageAlign: value,
- } )
+
+ setAttributes( {
+ featuredImageAlign:
+ value !== 'none' ? value : undefined,
+ } )
+ }
+ >
+ { imageAlignmentOptions.map(
+ ( { value, icon, label } ) => {
+ return (
+
+ );
}
- controls={ [ 'left', 'center', 'right' ] }
- isCollapsed={ false }
- />
-
+ ) }
+
Date: Fri, 9 Aug 2024 19:13:06 +0900
Subject: [PATCH 021/126] Post Editor: Force iframe editor when zoom-out mode
(#64316)
Co-authored-by: t-hamano
Co-authored-by: stokesman
---
.../edit-post/src/components/layout/use-should-iframe.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/edit-post/src/components/layout/use-should-iframe.js b/packages/edit-post/src/components/layout/use-should-iframe.js
index 03efae92c72f7d..248ea53109f250 100644
--- a/packages/edit-post/src/components/layout/use-should-iframe.js
+++ b/packages/edit-post/src/components/layout/use-should-iframe.js
@@ -4,6 +4,7 @@
import { store as editorStore } from '@wordpress/editor';
import { useSelect } from '@wordpress/data';
import { store as blocksStore } from '@wordpress/blocks';
+import { store as blockEditorStore } from '@wordpress/block-editor';
/**
* Internal dependencies
@@ -18,8 +19,10 @@ export function useShouldIframe() {
hasV3BlocksOnly,
isEditingTemplate,
hasMetaBoxes,
+ isZoomOutMode,
} = useSelect( ( select ) => {
const { getEditorSettings, getCurrentPostType } = select( editorStore );
+ const { __unstableGetEditorMode } = select( blockEditorStore );
const { getBlockTypes } = select( blocksStore );
const editorSettings = getEditorSettings();
return {
@@ -29,12 +32,14 @@ export function useShouldIframe() {
} ),
isEditingTemplate: getCurrentPostType() === 'wp_template',
hasMetaBoxes: select( editPostStore ).hasMetaBoxes(),
+ isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
};
}, [] );
return (
( ( hasV3BlocksOnly || ( isGutenbergPlugin && isBlockBasedTheme ) ) &&
! hasMetaBoxes ) ||
- isEditingTemplate
+ isEditingTemplate ||
+ isZoomOutMode
);
}
From 22e4cc2e24e89901d7b0b8099a7ecc4f695e04e2 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Fri, 9 Aug 2024 12:18:49 +0200
Subject: [PATCH 022/126] Composite: stabilize new ariakit implementation
(#63564)
* Point legacy exports directly to the source (instead of folder root)
* Swap default folder export to new version
* Apply compound component naming
* Export new version from the package
* Update (fix) private APIs exports
* Update composite implementation to use new compound naming
* Update references to Composite inside components package
* Update Storybook entry points for both legacy and current
* Fix Storybook generated docs
* Add todo
* Remove unncecessary code
* CHANGELOG
* README
* Add JSDocs to Composite exports
* Move current implementation out of `current` folder
* Fix import in the legacy implementation
* Update docs manifest
* Fix type in Storybook example
* Add JSDocs for Storybook docs
* Apply Overloaded naming convention
* Update README
* Fix typo
* Update legacy storybook title/id, make sure JSDocs refer to unstable version
* Derive types instead of importing them directly from ariakit
* Add JSDoc snippet for stable component
* Remove unnecessary JSDoc code
* Remove unnecessary display name
* Assign display names via Object.assign to comply with TS and get correct results in Storybook
* Update subcomponent TS ignore comment to align with other components
* Remove unnecessary store prop in circular option picker
Composite.Item should pick up the store from context without explicit prop
* Add first-party types, rewrite components with one unique forwardRef call
* Use the newly added types instead of using the Parameters<> util
* Fix Storybook story type
* Remove unnecessary ts-expect-error
* Use `CompositeStore` type directly
* Manual Storybook args table
* Tweak display name fallback
* README
* Mark `store` prop on `Composite` as required
---
Co-authored-by: ciampo
Co-authored-by: tyxla
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
docs/manifest.json | 6 +
packages/components/CHANGELOG.md | 4 +
.../src/alignment-matrix-control/cell.tsx | 6 +-
.../src/alignment-matrix-control/index.tsx | 6 +-
.../circular-option-picker-option.tsx | 11 +-
.../circular-option-picker.tsx | 2 +-
.../src/circular-option-picker/types.ts | 5 +-
packages/components/src/composite/README.md | 176 ++++++++++++++
.../components/src/composite/current/index.ts | 20 --
.../composite/current/stories/index.story.tsx | 86 -------
packages/components/src/composite/index.ts | 7 -
packages/components/src/composite/index.tsx | 177 ++++++++++++++
.../components/src/composite/legacy/index.tsx | 29 +--
.../composite/legacy/stories/index.story.tsx | 3 +-
.../src/composite/legacy/stories/utils.tsx | 19 ++
.../src/composite/stories/index.story.tsx | 218 ++++++++++++++++++
.../composite/{current => }/stories/utils.tsx | 13 +-
packages/components/src/composite/types.ts | 47 ++++
packages/components/src/composite/v2.ts | 4 -
packages/components/src/index.ts | 3 +-
packages/components/src/private-apis.ts | 18 +-
.../src/dataviews-layouts/list/index.tsx | 1 +
22 files changed, 700 insertions(+), 161 deletions(-)
create mode 100644 packages/components/src/composite/README.md
delete mode 100644 packages/components/src/composite/current/index.ts
delete mode 100644 packages/components/src/composite/current/stories/index.story.tsx
delete mode 100644 packages/components/src/composite/index.ts
create mode 100644 packages/components/src/composite/index.tsx
create mode 100644 packages/components/src/composite/stories/index.story.tsx
rename packages/components/src/composite/{current => }/stories/utils.tsx (86%)
create mode 100644 packages/components/src/composite/types.ts
delete mode 100644 packages/components/src/composite/v2.ts
diff --git a/docs/manifest.json b/docs/manifest.json
index 1704e6d711510f..b483449872cc76 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -767,6 +767,12 @@
"markdown_source": "../packages/components/src/combobox-control/README.md",
"parent": "components"
},
+ {
+ "title": "Composite",
+ "slug": "composite",
+ "markdown_source": "../packages/components/src/composite/README.md",
+ "parent": "components"
+ },
{
"title": "ConfirmDialog",
"slug": "confirm-dialog",
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 47afb0711090d8..dca5b6e03dedc3 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### New Features
+
+- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).
+
### Enhancements
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
diff --git a/packages/components/src/alignment-matrix-control/cell.tsx b/packages/components/src/alignment-matrix-control/cell.tsx
index 162ca879f1a7e5..6e045c26694f4e 100644
--- a/packages/components/src/alignment-matrix-control/cell.tsx
+++ b/packages/components/src/alignment-matrix-control/cell.tsx
@@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
-import { CompositeItem } from '../composite/v2';
+import { Composite } from '../composite';
import Tooltip from '../tooltip';
import { VisuallyHidden } from '../visually-hidden';
@@ -26,7 +26,7 @@ export default function Cell( {
return (
- }
>
@@ -35,7 +35,7 @@ export default function Cell( {
hidden element instead of aria-label. */ }
{ value }
-
+
);
}
diff --git a/packages/components/src/alignment-matrix-control/index.tsx b/packages/components/src/alignment-matrix-control/index.tsx
index eaec8a285b0c57..1d22c3560625db 100644
--- a/packages/components/src/alignment-matrix-control/index.tsx
+++ b/packages/components/src/alignment-matrix-control/index.tsx
@@ -13,7 +13,7 @@ import { useInstanceId } from '@wordpress/compose';
* Internal dependencies
*/
import Cell from './cell';
-import { Composite, CompositeRow, useCompositeStore } from '../composite/v2';
+import { Composite, useCompositeStore } from '../composite';
import { Root, Row } from './styles/alignment-matrix-control-styles';
import AlignmentMatrixControlIcon from './icon';
import { GRID, getItemId, getItemValue } from './utils';
@@ -87,7 +87,7 @@ export function AlignmentMatrixControl( {
}
>
{ GRID.map( ( cells, index ) => (
- } key={ index }>
+ } key={ index }>
{ cells.map( ( cell ) => {
const cellId = getItemId( baseId, cell );
const isActive = cellId === activeId;
@@ -101,7 +101,7 @@ export function AlignmentMatrixControl( {
/>
);
} ) }
-
+
) ) }
);
diff --git a/packages/components/src/circular-option-picker/circular-option-picker-option.tsx b/packages/components/src/circular-option-picker/circular-option-picker-option.tsx
index 6c00a0e5d0bf1a..35a2f427134f40 100644
--- a/packages/components/src/circular-option-picker/circular-option-picker-option.tsx
+++ b/packages/components/src/circular-option-picker/circular-option-picker-option.tsx
@@ -16,9 +16,9 @@ import { Icon, check } from '@wordpress/icons';
*/
import { CircularOptionPickerContext } from './circular-option-picker-context';
import Button from '../button';
-import { CompositeItem } from '../composite/v2';
+import { Composite } from '../composite';
import Tooltip from '../tooltip';
-import type { OptionProps, CircularOptionPickerCompositeStore } from './types';
+import type { OptionProps } from './types';
function UnforwardedOptionAsButton(
props: {
@@ -45,7 +45,9 @@ function UnforwardedOptionAsOption(
id: string;
className?: string;
isSelected?: boolean;
- compositeStore: CircularOptionPickerCompositeStore;
+ compositeStore: NonNullable<
+ React.ComponentProps< typeof Composite >[ 'store' ]
+ >;
},
forwardedRef: ForwardedRef< any >
) {
@@ -57,7 +59,7 @@ function UnforwardedOptionAsOption(
}
return (
-
}
- store={ compositeStore }
id={ id }
/>
);
diff --git a/packages/components/src/circular-option-picker/circular-option-picker.tsx b/packages/components/src/circular-option-picker/circular-option-picker.tsx
index cd2ddcf90d7ce0..c1e719f2d4f665 100644
--- a/packages/components/src/circular-option-picker/circular-option-picker.tsx
+++ b/packages/components/src/circular-option-picker/circular-option-picker.tsx
@@ -13,7 +13,7 @@ import { isRTL } from '@wordpress/i18n';
* Internal dependencies
*/
import { CircularOptionPickerContext } from './circular-option-picker-context';
-import { Composite, useCompositeStore } from '../composite/v2';
+import { Composite, useCompositeStore } from '../composite';
import type {
CircularOptionPickerProps,
ListboxCircularOptionPickerProps,
diff --git a/packages/components/src/circular-option-picker/types.ts b/packages/components/src/circular-option-picker/types.ts
index 519d81d5905107..e23ff4165f0580 100644
--- a/packages/components/src/circular-option-picker/types.ts
+++ b/packages/components/src/circular-option-picker/types.ts
@@ -14,7 +14,7 @@ import type { Icon } from '@wordpress/icons';
import type { ButtonAsButtonProps } from '../button/types';
import type { DropdownProps } from '../dropdown/types';
import type { WordPressComponentProps } from '../context';
-import type { CompositeStore } from '../composite/v2';
+import type { Composite } from '../composite';
type CommonCircularOptionPickerProps = {
/**
@@ -123,8 +123,7 @@ export type OptionProps = Omit<
>;
};
-export type CircularOptionPickerCompositeStore = CompositeStore;
export type CircularOptionPickerContextProps = {
baseId?: string;
- compositeStore?: CircularOptionPickerCompositeStore;
+ compositeStore?: React.ComponentProps< typeof Composite >[ 'store' ];
};
diff --git a/packages/components/src/composite/README.md b/packages/components/src/composite/README.md
new file mode 100644
index 00000000000000..59953f1273a054
--- /dev/null
+++ b/packages/components/src/composite/README.md
@@ -0,0 +1,176 @@
+# `Composite`
+
+`Composite` provides a single tab stop on the page and allows navigation through the focusable descendants with arrow keys. This abstract component is based on the [WAI-ARIA Composite Role](https://w3c.github.io/aria/#composite).
+
+See the [Ariakit docs for the `Composite` component](https://ariakit.org/components/composite).
+
+## Usage
+
+```jsx
+const store = useCompositeStore();
+
+
+ Label
+ Item 1
+ Item 2
+
+
+```
+
+## Hooks
+
+### `useCompositeStore`
+
+Creates a composite store.
+
+#### Props
+
+##### `activeId`: `string | null`
+
+The current active item id. The active item is the element within the composite widget that has either DOM or virtual focus.
+
+- Required: no
+
+##### `defaultActiveId`: `string | null`
+
+The composite item id that should be active by default when the composite widget is rendered. If `null`, the composite element itself will have focus and users will be able to navigate to it using arrow keys. If `undefined`, the first enabled item will be focused.
+
+- Required: no
+
+##### `setActiveId`: `((activeId: string | null | undefined) => void)`
+
+A callback that gets called when the activeId state changes.
+
+- Required: no
+
+##### `focusLoop`: `boolean | 'horizontal' | 'vertical' | 'both'`
+
+Determines how the focus behaves when the user reaches the end of the composite widget.
+
+- Required: no
+- Default: `false`
+
+##### `focusShift`: `boolean`
+
+Works only on two-dimensional composite widgets. If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.
+
+- Required: no
+- Default: `false`
+
+##### `focusWrap`: `boolean`
+
+Works only on two-dimensional composite widgets. If enabled, moving to the next item from the last one in a row or column will focus on the first item in the next row or column and vice-versa.
+
+- Required: no
+- Default: `false`
+
+##### `virtualFocus`: `boolean`
+
+If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite element while its items receive virtual focus. In both scenarios, the item in focus will carry the data-active-item attribute.
+
+- Required: no
+- Default: `false`
+
+##### `orientation`: `'horizontal' | 'vertical' | 'both'`
+
+Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus. It doesn't have any effect on two-dimensional composites.
+
+- Required: no
+- Default: `'both'`
+
+##### `rtl`: `boolean`
+
+Determines how the next and previous functions will behave. If rtl is set to true, they will be inverted. This only affects the composite widget behavior. You still need to set dir=`rtl` on HTML/CSS.
+
+- Required: no
+- Default: `false`
+
+## Components
+
+### `Composite`
+
+Renders a composite widget.
+
+#### Props
+
+##### `store`: `CompositeStore`
+
+Object returned by the `useCompositeStore` hook.
+
+- Required: yes
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
+
+### `Composite.Group`
+
+Renders a group element for composite items.
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
+
+### `Composite.GroupLabel`
+
+Renders a label in a composite group. This component must be wrapped with `Composite.Group` so the `aria-labelledby` prop is properly set on the composite group element.
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
+
+### `Composite.Item`
+
+Renders a composite item.
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
+
+### `Composite.Row`
+
+Renders a composite row. Wrapping `Composite.Item` elements within `Composite.Row` will create a two-dimensional composite widget, such as a grid.
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
diff --git a/packages/components/src/composite/current/index.ts b/packages/components/src/composite/current/index.ts
deleted file mode 100644
index 96379f00296516..00000000000000
--- a/packages/components/src/composite/current/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Composite is a component that may contain navigable items represented by
- * CompositeItem. It's inspired by the WAI-ARIA Composite Role and implements
- * all the keyboard navigation mechanisms to ensure that there's only one
- * tab stop for the whole Composite element. This means that it can behave as
- * a roving tabindex or aria-activedescendant container.
- *
- * @see https://ariakit.org/components/composite
- */
-
-export {
- Composite,
- CompositeGroup,
- CompositeGroupLabel,
- CompositeItem,
- CompositeRow,
- useCompositeStore,
-} from '@ariakit/react';
-
-export type { CompositeStore, CompositeStoreProps } from '@ariakit/react';
diff --git a/packages/components/src/composite/current/stories/index.story.tsx b/packages/components/src/composite/current/stories/index.story.tsx
deleted file mode 100644
index 335ebc3244c918..00000000000000
--- a/packages/components/src/composite/current/stories/index.story.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * External dependencies
- */
-import type { Meta, StoryFn } from '@storybook/react';
-
-/**
- * WordPress dependencies
- */
-import { isRTL } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import {
- Composite,
- CompositeGroup,
- CompositeRow,
- CompositeItem,
- useCompositeStore,
-} from '..';
-import { UseCompositeStorePlaceholder, transform } from './utils';
-
-const meta: Meta< typeof UseCompositeStorePlaceholder > = {
- title: 'Components/Composite (V2)',
- component: UseCompositeStorePlaceholder,
- subcomponents: {
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- Composite,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- CompositeGroup,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- CompositeRow,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- CompositeItem,
- },
- tags: [ 'status-private' ],
- parameters: {
- docs: {
- canvas: { sourceState: 'shown' },
- source: { transform },
- extractArgTypes: ( component: React.FunctionComponent ) => {
- const name = component.displayName;
- const path = name
- ?.replace(
- /([a-z])([A-Z])/g,
- ( _, a, b ) => `${ a }-${ b.toLowerCase() }`
- )
- .toLowerCase();
- const url = `https://ariakit.org/reference/${ path }`;
- return {
- props: {
- name: 'Props',
- description: `See Ariakit docs for ${ name }
`,
- table: { type: { summary: undefined } },
- },
- };
- },
- },
- },
-};
-export default meta;
-
-export const Default: StoryFn< typeof Composite > = ( { ...initialState } ) => {
- const rtl = isRTL();
- const store = useCompositeStore( { rtl, ...initialState } );
-
- return (
-
-
- Item A1
- Item A2
- Item A3
-
-
- Item B1
- Item B2
- Item B3
-
-
- Item C1
- Item C2
- Item C3
-
-
- );
-};
diff --git a/packages/components/src/composite/index.ts b/packages/components/src/composite/index.ts
deleted file mode 100644
index aa06a6adf36ef2..00000000000000
--- a/packages/components/src/composite/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// Originally this pointed at a Reakit implementation of
-// `Composite`, but we are removing Reakit entirely from the
-// codebase. We will continue to support the Reakit API
-// through the 'legacy' version, which uses Ariakit under
-// the hood.
-
-export * from './legacy';
diff --git a/packages/components/src/composite/index.tsx b/packages/components/src/composite/index.tsx
new file mode 100644
index 00000000000000..9496bdb9e98664
--- /dev/null
+++ b/packages/components/src/composite/index.tsx
@@ -0,0 +1,177 @@
+/**
+ * Composite is a component that may contain navigable items represented by
+ * Composite.Item. It's inspired by the WAI-ARIA Composite Role and implements
+ * all the keyboard navigation mechanisms to ensure that there's only one
+ * tab stop for the whole Composite element. This means that it can behave as
+ * a roving tabindex or aria-activedescendant container.
+ *
+ * @see https://ariakit.org/components/composite
+ */
+
+/**
+ * External dependencies
+ */
+import * as Ariakit from '@ariakit/react';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { WordPressComponentProps } from '../context';
+import type {
+ CompositeStoreProps,
+ CompositeProps,
+ CompositeGroupProps,
+ CompositeGroupLabelProps,
+ CompositeItemProps,
+ CompositeRowProps,
+} from './types';
+
+/**
+ * Creates a composite store.
+ * @param props
+ * @see https://ariakit.org/reference/use-composite-store
+ * @example
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ * Item
+ * Item
+ * Item
+ *
+ * ```
+ */
+export function useCompositeStore( props: CompositeStoreProps ) {
+ return Ariakit.useCompositeStore( props );
+}
+
+const Group = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< CompositeGroupProps, 'div', false >
+>( function CompositeGroup( props, ref ) {
+ return ;
+} );
+Group.displayName = 'Composite.Group';
+
+const GroupLabel = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< CompositeGroupLabelProps, 'div', false >
+>( function CompositeGroupLabel( props, ref ) {
+ return ;
+} );
+GroupLabel.displayName = 'Composite.GroupLabel';
+
+const Item = forwardRef<
+ HTMLButtonElement,
+ WordPressComponentProps< CompositeItemProps, 'button', false >
+>( function CompositeItem( props, ref ) {
+ return ;
+} );
+Item.displayName = 'Composite.Item';
+
+const Row = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< CompositeRowProps, 'div', false >
+>( function CompositeRow( props, ref ) {
+ return ;
+} );
+Row.displayName = 'Composite.Row';
+
+/**
+ * Renders a composite widget.
+ * @see https://ariakit.org/reference/composite
+ * @example
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ * Item 1
+ * Item 2
+ *
+ * ```
+ */
+export const Composite = Object.assign(
+ forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< CompositeProps, 'div', false >
+ >( function CompositeRow( props, ref ) {
+ return ;
+ } ),
+ {
+ displayName: 'Composite',
+ /**
+ * Renders a group element for composite items.
+ * @see https://ariakit.org/reference/composite-group
+ * @example
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ *
+ * Label
+ * Item 1
+ * Item 2
+ *
+ *
+ * ```
+ */
+ Group,
+ /**
+ * Renders a label in a composite group. This component must be wrapped with
+ * `Composite.Group` so the `aria-labelledby` prop is properly set on the
+ * composite group element.
+ * @see https://ariakit.org/reference/composite-group-label
+ * @example
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ *
+ * Label
+ * Item 1
+ * Item 2
+ *
+ *
+ * ```
+ */
+ GroupLabel,
+ /**
+ * Renders a composite item.
+ * @see https://ariakit.org/reference/composite-item
+ * @example
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ * Item 1
+ * Item 2
+ * Item 3
+ *
+ * ```
+ */
+ Item,
+ /**
+ * Renders a composite row. Wrapping `Composite.Item` elements within
+ * `Composite.Row` will create a two-dimensional composite widget, such as a
+ * grid.
+ * @see https://ariakit.org/reference/composite-row
+ * @example
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ *
+ * Item 1.1
+ * Item 1.2
+ * Item 1.3
+ *
+ *
+ * Item 2.1
+ * Item 2.2
+ * Item 2.3
+ *
+ *
+ * ```
+ */
+ Row,
+ }
+);
diff --git a/packages/components/src/composite/legacy/index.tsx b/packages/components/src/composite/legacy/index.tsx
index 5c5c674b5086b8..dffdc1a2066d47 100644
--- a/packages/components/src/composite/legacy/index.tsx
+++ b/packages/components/src/composite/legacy/index.tsx
@@ -5,6 +5,11 @@
* tab stop for the whole Composite element. This means that it can behave as
* a roving tabindex or aria-activedescendant container.
*
+ * This file aims at providing components that are as close as possible to the
+ * original `reakit`-based implementation (which was removed from the codebase),
+ * although it is recommended that consumers of the package switch to the stable,
+ * un-prefixed, `ariakit`-based version of `Composite`.
+ *
* @see https://ariakit.org/components/composite
*/
@@ -16,7 +21,7 @@ import { forwardRef } from '@wordpress/element';
/**
* Internal dependencies
*/
-import * as Current from '../current';
+import { Composite as Current, useCompositeStore } from '..';
import { useInstanceId } from '@wordpress/compose';
type Orientation = 'horizontal' | 'vertical';
@@ -73,7 +78,7 @@ export interface LegacyStateOptions {
type Component = React.FunctionComponent< any >;
-type CompositeStore = ReturnType< typeof Current.useCompositeStore >;
+type CompositeStore = ReturnType< typeof useCompositeStore >;
type CompositeStoreState = { store: CompositeStore };
export type CompositeState = CompositeStoreState &
Required< Pick< LegacyStateOptions, 'baseId' > >;
@@ -93,9 +98,9 @@ type CompositeComponent< C extends Component > = (
) => React.ReactElement;
type CompositeComponentProps = CompositeState &
(
- | ComponentProps< typeof Current.CompositeGroup >
- | ComponentProps< typeof Current.CompositeItem >
- | ComponentProps< typeof Current.CompositeRow >
+ | ComponentProps< typeof Current.Group >
+ | ComponentProps< typeof Current.Item >
+ | ComponentProps< typeof Current.Row >
);
function mapLegacyStatePropsToComponentProps(
@@ -145,19 +150,15 @@ function proxyComposite< C extends Component >(
// provided role, and returning the appropriate component.
const unproxiedCompositeGroup = forwardRef<
any,
- React.ComponentPropsWithoutRef<
- typeof Current.CompositeGroup | typeof Current.CompositeRow
- >
+ React.ComponentPropsWithoutRef< typeof Current.Group | typeof Current.Row >
>( ( { role, ...props }, ref ) => {
- const Component =
- role === 'row' ? Current.CompositeRow : Current.CompositeGroup;
+ const Component = role === 'row' ? Current.Row : Current.Group;
return ;
} );
-unproxiedCompositeGroup.displayName = 'CompositeGroup';
-export const Composite = proxyComposite( Current.Composite, { baseId: 'id' } );
+export const Composite = proxyComposite( Current, { baseId: 'id' } );
export const CompositeGroup = proxyComposite( unproxiedCompositeGroup );
-export const CompositeItem = proxyComposite( Current.CompositeItem, {
+export const CompositeItem = proxyComposite( Current.Item, {
focusable: 'accessibleWhenDisabled',
} );
@@ -178,7 +179,7 @@ export function useCompositeState(
return {
baseId: useInstanceId( Composite, 'composite', baseId ),
- store: Current.useCompositeStore( {
+ store: useCompositeStore( {
defaultActiveId,
rtl,
orientation,
diff --git a/packages/components/src/composite/legacy/stories/index.story.tsx b/packages/components/src/composite/legacy/stories/index.story.tsx
index e46d656a16810e..1b8e07e9bbf560 100644
--- a/packages/components/src/composite/legacy/stories/index.story.tsx
+++ b/packages/components/src/composite/legacy/stories/index.story.tsx
@@ -15,7 +15,8 @@ import {
import { UseCompositeStatePlaceholder, transform } from './utils';
const meta: Meta< typeof UseCompositeStatePlaceholder > = {
- title: 'Components/Composite',
+ title: 'Components (Deprecated)/Composite (Unstable)',
+ id: 'components-composite-unstable',
component: UseCompositeStatePlaceholder,
subcomponents: {
Composite,
diff --git a/packages/components/src/composite/legacy/stories/utils.tsx b/packages/components/src/composite/legacy/stories/utils.tsx
index 06edd348634695..2fb51c845f9fbe 100644
--- a/packages/components/src/composite/legacy/stories/utils.tsx
+++ b/packages/components/src/composite/legacy/stories/utils.tsx
@@ -8,6 +8,25 @@ import type { StoryContext } from '@storybook/react';
*/
import type { LegacyStateOptions } from '..';
+/**
+ * Renders a composite widget.
+ *
+ * This unstable component is deprecated. Use `Composite` instead.
+ *
+ * ```jsx
+ * import {
+ * __unstableUseCompositeState as useCompositeState,
+ * __unstableComposite as Composite,
+ * __unstableCompositeItem as CompositeItem,
+ * } from '@wordpress/components';
+ *
+ * const state = useCompositeState();
+ *
+ * Item 1
+ * Item 2
+ * ;
+ * ```
+ */
export function UseCompositeStatePlaceholder( props: LegacyStateOptions ) {
return (
diff --git a/packages/components/src/composite/stories/index.story.tsx b/packages/components/src/composite/stories/index.story.tsx
new file mode 100644
index 00000000000000..b143c1f7db05f7
--- /dev/null
+++ b/packages/components/src/composite/stories/index.story.tsx
@@ -0,0 +1,218 @@
+/**
+ * External dependencies
+ */
+import type { Meta, StoryFn } from '@storybook/react';
+
+/**
+ * WordPress dependencies
+ */
+import { isRTL } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { Composite, useCompositeStore } from '..';
+import { UseCompositeStorePlaceholder, transform } from './utils';
+
+const meta: Meta< typeof UseCompositeStorePlaceholder > = {
+ title: 'Components/Composite',
+ component: UseCompositeStorePlaceholder,
+ subcomponents: {
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ Composite,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ 'Composite.Group': Composite.Group,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ 'Composite.GroupLabel': Composite.GroupLabel,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ 'Composite.Row': Composite.Row,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ 'Composite.Item': Composite.Item,
+ },
+ parameters: {
+ docs: {
+ canvas: { sourceState: 'shown' },
+ source: { transform },
+ extractArgTypes: ( component: React.FunctionComponent ) => {
+ const commonArgTypes = {
+ render: {
+ name: 'render',
+ description:
+ 'Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.',
+ table: {
+ type: {
+ summary:
+ 'RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>',
+ },
+ },
+ },
+ children: {
+ name: 'children',
+ description: 'The contents of the component.',
+ table: { type: { summary: 'React.ReactNode' } },
+ },
+ };
+ const argTypes = {
+ useCompositeStore: {
+ activeId: {
+ name: 'activeId',
+ description:
+ 'The current active item id. The active item is the element within the composite widget that has either DOM or virtual focus.',
+ table: { type: { summary: 'string | null' } },
+ },
+ defaultActiveId: {
+ name: 'defaultActiveId',
+ description:
+ 'The composite item id that should be active by default when the composite widget is rendered. If `null`, the composite element itself will have focus and users will be able to navigate to it using arrow keys. If `undefined`, the first enabled item will be focused.',
+ table: { type: { summary: 'string | null' } },
+ },
+ setActiveId: {
+ name: 'setActiveId',
+ description:
+ 'A callback that gets called when the activeId state changes.',
+ table: {
+ type: {
+ summary:
+ '((activeId: string | null | undefined) => void)',
+ },
+ },
+ },
+ focusLoop: {
+ name: 'focusLoop',
+ description:
+ 'Determines how the focus behaves when the user reaches the end of the composite widget.',
+ table: {
+ defaultValue: {
+ summary: 'false',
+ },
+ type: {
+ summary:
+ "boolean | 'horizontal' | 'vertical' | 'both'",
+ },
+ },
+ },
+ focusShift: {
+ name: 'focusShift',
+ description:
+ "Works only on two-dimensional composite widgets. If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.",
+ table: {
+ defaultValue: {
+ summary: 'false',
+ },
+ type: {
+ summary: 'boolean',
+ },
+ },
+ },
+ focusWrap: {
+ name: 'focusWrap',
+ description:
+ 'Works only on two-dimensional composite widgets. If enabled, moving to the next item from the last one in a row or column will focus on the first item in the next row or column and vice-versa.',
+ table: {
+ defaultValue: {
+ summary: 'false',
+ },
+ type: {
+ summary: 'boolean',
+ },
+ },
+ },
+ virtualFocus: {
+ name: 'virtualFocus',
+ description:
+ 'If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite element while its items receive virtual focus. In both scenarios, the item in focus will carry the data-active-item attribute.',
+ table: {
+ defaultValue: {
+ summary: 'false',
+ },
+ type: {
+ summary: 'boolean',
+ },
+ },
+ },
+ orientation: {
+ name: 'orientation',
+ description:
+ "Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus. It doesn't have any effect on two-dimensional composites.",
+ table: {
+ defaultValue: {
+ summary: "'both'",
+ },
+ type: {
+ summary:
+ "'horizontal' | 'vertical' | 'both'",
+ },
+ },
+ },
+ rtl: {
+ name: 'rtl',
+ description:
+ 'Determines how the next and previous functions will behave. If rtl is set to true, they will be inverted. This only affects the composite widget behavior. You still need to set dir="rtl" on HTML/CSS.',
+ table: {
+ defaultValue: {
+ summary: 'false',
+ },
+ type: {
+ summary: 'boolean',
+ },
+ },
+ },
+ },
+ Composite: {
+ ...commonArgTypes,
+ store: {
+ name: 'store',
+ description:
+ 'Object returned by the `useCompositeStore` hook.',
+ table: {
+ type: {
+ summary:
+ 'CompositeStore',
+ },
+ },
+ type: { required: true },
+ },
+ },
+ 'Composite.Group': commonArgTypes,
+ 'Composite.GroupLabel': commonArgTypes,
+ 'Composite.Row': commonArgTypes,
+ 'Composite.Item': commonArgTypes,
+ };
+
+ const name = component.displayName ?? '';
+
+ return name in argTypes
+ ? argTypes[ name as keyof typeof argTypes ]
+ : {};
+ },
+ },
+ },
+};
+export default meta;
+
+export const Default: StoryFn< typeof UseCompositeStorePlaceholder > = (
+ storeProps
+) => {
+ const rtl = isRTL();
+ const store = useCompositeStore( { rtl, ...storeProps } );
+
+ return (
+
+
+ Item A1
+ Item A2
+ Item A3
+
+
+ Item B1
+ Item B2
+ Item B3
+
+
+ Item C1
+ Item C2
+ Item C3
+
+
+ );
+};
diff --git a/packages/components/src/composite/current/stories/utils.tsx b/packages/components/src/composite/stories/utils.tsx
similarity index 86%
rename from packages/components/src/composite/current/stories/utils.tsx
rename to packages/components/src/composite/stories/utils.tsx
index 4b2d1bba4b312b..425fb9a905bcff 100644
--- a/packages/components/src/composite/current/stories/utils.tsx
+++ b/packages/components/src/composite/stories/utils.tsx
@@ -6,8 +6,19 @@ import type { StoryContext } from '@storybook/react';
/**
* Internal dependencies
*/
-import type { CompositeStoreProps } from '..';
+import type { CompositeStoreProps } from '../types';
+/**
+ * Renders a composite widget.
+ *
+ * ```jsx
+ * const store = useCompositeStore();
+ *
+ * Item 1
+ * Item 2
+ *
+ * ```
+ */
export function UseCompositeStorePlaceholder( props: CompositeStoreProps ) {
return (
diff --git a/packages/components/src/composite/types.ts b/packages/components/src/composite/types.ts
new file mode 100644
index 00000000000000..438d1caaa94f8a
--- /dev/null
+++ b/packages/components/src/composite/types.ts
@@ -0,0 +1,47 @@
+/**
+ * External dependencies
+ */
+import type * as Ariakit from '@ariakit/react';
+
+export type CompositeStoreProps = Pick<
+ Ariakit.CompositeStoreProps,
+ | 'activeId'
+ | 'defaultActiveId'
+ | 'setActiveId'
+ | 'focusLoop'
+ | 'focusShift'
+ | 'focusWrap'
+ | 'virtualFocus'
+ | 'orientation'
+ | 'rtl'
+>;
+
+export type CompositeProps = Pick<
+ Ariakit.CompositeProps,
+ 'render' | 'children'
+> & {
+ /**
+ * Object returned by the `useCompositeStore` hook.
+ */
+ store: Ariakit.CompositeStore;
+};
+
+export type CompositeGroupProps = Pick<
+ Ariakit.CompositeGroupProps,
+ 'render' | 'children'
+>;
+
+export type CompositeGroupLabelProps = Pick<
+ Ariakit.CompositeGroupLabelProps,
+ 'render' | 'children'
+>;
+
+export type CompositeItemProps = Pick<
+ Ariakit.CompositeItemProps,
+ 'render' | 'children'
+>;
+
+export type CompositeRowProps = Pick<
+ Ariakit.CompositeRowProps,
+ 'render' | 'children'
+>;
diff --git a/packages/components/src/composite/v2.ts b/packages/components/src/composite/v2.ts
deleted file mode 100644
index 38d3f628d368b6..00000000000000
--- a/packages/components/src/composite/v2.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// Although we have migrated away from Reakit, the 'current'
-// Ariakit implementation is still considered a v2.
-
-export * from './current';
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index f0ea4a4b7e86b8..4c724a461e6775 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -61,7 +61,8 @@ export {
CompositeGroup as __unstableCompositeGroup,
CompositeItem as __unstableCompositeItem,
useCompositeState as __unstableUseCompositeState,
-} from './composite';
+} from './composite/legacy';
+export { Composite } from './composite';
export { ConfirmDialog as __experimentalConfirmDialog } from './confirm-dialog';
export { default as CustomSelectControl } from './custom-select-control';
export { default as Dashicon } from './dashicon';
diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts
index 5ff39ba364a041..699911e5ba046b 100644
--- a/packages/components/src/private-apis.ts
+++ b/packages/components/src/private-apis.ts
@@ -1,13 +1,7 @@
/**
* Internal dependencies
*/
-import {
- Composite as CompositeV2,
- CompositeGroup as CompositeGroupV2,
- CompositeItem as CompositeItemV2,
- CompositeRow as CompositeRowV2,
- useCompositeStore as useCompositeStoreV2,
-} from './composite/v2';
+import { Composite, useCompositeStore } from './composite';
import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils';
import { createPrivateSlotFill } from './slot-fill';
import {
@@ -28,11 +22,11 @@ import { lock } from './lock-unlock';
export const privateApis = {};
lock( privateApis, {
- CompositeV2,
- CompositeGroupV2,
- CompositeItemV2,
- CompositeRowV2,
- useCompositeStoreV2,
+ CompositeV2: Composite,
+ CompositeGroupV2: Composite.Group,
+ CompositeItemV2: Composite.Item,
+ CompositeRowV2: Composite.Row,
+ useCompositeStoreV2: useCompositeStore,
__experimentalPopoverLegacyPositionToPlacement,
createPrivateSlotFill,
ComponentsContext,
diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx
index 00939a12984856..70eaf3af1cd619 100644
--- a/packages/dataviews/src/dataviews-layouts/list/index.tsx
+++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx
@@ -2,6 +2,7 @@
* External dependencies
*/
import clsx from 'clsx';
+// TODO: use the @wordpress/components one once public
// Import CompositeStore type, which is not exported from @wordpress/components.
// eslint-disable-next-line no-restricted-imports
import type { CompositeStore } from '@ariakit/react';
From 34b5219842367bb111cc010cb1db9a3a593812dd Mon Sep 17 00:00:00 2001
From: Ben Dwyer
Date: Fri, 9 Aug 2024 11:30:20 +0100
Subject: [PATCH 023/126] Zoom Out: Don't hide the insertion point when
hovering patterns (#64392)
Co-authored-by: scruffian
Co-authored-by: MaggieCabrera
---
.../block-editor/src/components/inserter/menu.js | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js
index e0bc29d62e1b9a..52e4e3062e9024 100644
--- a/packages/block-editor/src/components/inserter/menu.js
+++ b/packages/block-editor/src/components/inserter/menu.js
@@ -109,6 +109,7 @@ function InserterMenu(
const onInsertPattern = useCallback(
( blocks, patternName ) => {
+ onToggleInsertionPoint( false );
onInsertBlocks( blocks, { patternName } );
onSelect();
},
@@ -123,13 +124,6 @@ function InserterMenu(
[ onToggleInsertionPoint, setHoveredItem ]
);
- const onHoverPattern = useCallback(
- ( item ) => {
- onToggleInsertionPoint( !! item );
- },
- [ onToggleInsertionPoint ]
- );
-
const onClickPatternCategory = useCallback(
( patternCategory, filter ) => {
setSelectedPatternCategory( patternCategory );
@@ -176,7 +170,6 @@ function InserterMenu(
filterValue={ delayedFilterValue }
onSelect={ onSelect }
onHover={ onHover }
- onHoverPattern={ onHoverPattern }
rootClientId={ rootClientId }
clientId={ clientId }
isAppender={ isAppender }
@@ -199,7 +192,6 @@ function InserterMenu(
delayedFilterValue,
onSelect,
onHover,
- onHoverPattern,
shouldFocusBlock,
clientId,
rootClientId,
@@ -249,7 +241,6 @@ function InserterMenu(
Date: Fri, 9 Aug 2024 19:56:49 +0900
Subject: [PATCH 024/126] DimensionControl: Add flag to remove bottom margin
(#64346)
* DimensionControl: Add flag to remove bottom margin
* Add lint rule
* Update in block-editor readme
* Add changelog
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.eslintrc.js | 1 +
.../components/responsive-block-control/README.md | 1 +
packages/components/CHANGELOG.md | 1 +
packages/components/src/dimension-control/README.md | 9 +++++++++
packages/components/src/dimension-control/index.tsx | 3 +++
packages/components/src/dimension-control/types.ts | 12 +++++-------
6 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 81408499bd34f4..cb669fb4177206 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -292,6 +292,7 @@ module.exports = {
...[
'CheckboxControl',
'ComboboxControl',
+ 'DimensionControl',
'FocalPointPicker',
'RangeControl',
'SearchControl',
diff --git a/packages/block-editor/src/components/responsive-block-control/README.md b/packages/block-editor/src/components/responsive-block-control/README.md
index 1d99caa024a419..379e245f03ff37 100644
--- a/packages/block-editor/src/components/responsive-block-control/README.md
+++ b/packages/block-editor/src/components/responsive-block-control/README.md
@@ -64,6 +64,7 @@ registerBlockType( 'my-plugin/my-block', {
const paddingControl = ( labelComponent, viewport ) => {
return (
setPaddingSize( value ) }
@@ -91,3 +92,11 @@ A callback which is triggered when a spacing size value changes (is selected/cli
- **Required:** No
A string of classes to be added to the control component.
+
+### __nextHasNoMarginBottom
+
+Start opting into the new margin-free styles that will become the default in a future version.
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
diff --git a/packages/components/src/dimension-control/index.tsx b/packages/components/src/dimension-control/index.tsx
index 8ba0de0ce0599c..114ebe987dd35d 100644
--- a/packages/components/src/dimension-control/index.tsx
+++ b/packages/components/src/dimension-control/index.tsx
@@ -31,6 +31,7 @@ import type { SelectControlSingleSelectionProps } from '../select-control/types'
*
* return (
* setPaddingSize( value ) }
@@ -43,6 +44,7 @@ import type { SelectControlSingleSelectionProps } from '../select-control/types'
export function DimensionControl( props: DimensionControlProps ) {
const {
__next40pxDefaultSize = false,
+ __nextHasNoMarginBottom = false,
label,
value,
sizes = sizesTable,
@@ -87,6 +89,7 @@ export function DimensionControl( props: DimensionControlProps ) {
return (
& {
/**
* Label for the control.
*/
@@ -45,10 +49,4 @@ export type DimensionControlProps = {
* @default ''
*/
className?: string;
- /**
- * Start opting into the larger default height that will become the default size in a future version.
- *
- * @default false
- */
- __next40pxDefaultSize?: boolean;
};
From 00a965cc6d299b049c9ce9f317295cf9302deccf Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Fri, 9 Aug 2024 15:13:15 +0400
Subject: [PATCH 025/126] Gallery: Remove 'withNotices' HoC (#64384)
* Gallery: Remove 'withNotices' HoC
* Relocate 'withViewportMatch' closer to its only usage
Co-authored-by: Mamaduka
Co-authored-by: geriux
Co-authored-by: youknowriad
---
packages/block-library/src/gallery/edit.js | 9 +--------
packages/block-library/src/gallery/gallery.native.js | 3 ++-
2 files changed, 3 insertions(+), 9 deletions(-)
diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js
index f2faa40110dd13..1a7ce5fae04f8c 100644
--- a/packages/block-library/src/gallery/edit.js
+++ b/packages/block-library/src/gallery/edit.js
@@ -6,7 +6,6 @@ import clsx from 'clsx';
/**
* WordPress dependencies
*/
-import { compose } from '@wordpress/compose';
import {
BaseControl,
PanelBody,
@@ -17,7 +16,6 @@ import {
MenuGroup,
MenuItem,
ToolbarDropdownMenu,
- withNotices,
} from '@wordpress/components';
import {
store as blockEditorStore,
@@ -31,7 +29,6 @@ import {
import { Platform, useEffect, useMemo } from '@wordpress/element';
import { __, _x, sprintf } from '@wordpress/i18n';
import { useSelect, useDispatch } from '@wordpress/data';
-import { withViewportMatch } from '@wordpress/viewport';
import { View } from '@wordpress/primitives';
import { createBlock } from '@wordpress/blocks';
import { createBlobURL } from '@wordpress/blob';
@@ -98,7 +95,7 @@ const MOBILE_CONTROL_PROPS_RANGE_CONTROL = Platform.isNative
const DEFAULT_BLOCK = { name: 'core/image' };
const EMPTY_ARRAY = [];
-function GalleryEdit( props ) {
+export default function GalleryEdit( props ) {
const {
setAttributes,
attributes,
@@ -696,7 +693,3 @@ function GalleryEdit( props ) {
>
);
}
-export default compose( [
- withNotices,
- withViewportMatch( { isNarrow: '< small' } ),
-] )( GalleryEdit );
diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js
index 7fc280809db843..a5fa2db4fcf029 100644
--- a/packages/block-library/src/gallery/gallery.native.js
+++ b/packages/block-library/src/gallery/gallery.native.js
@@ -22,6 +22,7 @@ import { useState, useEffect } from '@wordpress/element';
import { mediaUploadSync } from '@wordpress/react-native-bridge';
import { WIDE_ALIGNMENTS } from '@wordpress/components';
import { useResizeObserver } from '@wordpress/compose';
+import { withViewportMatch } from '@wordpress/viewport';
const TILE_SPACING = 8;
@@ -120,4 +121,4 @@ export const Gallery = ( props ) => {
);
};
-export default Gallery;
+export default withViewportMatch( { isNarrow: '< small' } )( Gallery );
From d31b30ed135d06963a7036206a5f4855880f63f2 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Fri, 9 Aug 2024 14:31:13 +0200
Subject: [PATCH 026/126] DataViews Quick Edit: Add the PostActions dropdown
menu (#64393)
Co-authored-by: youknowriad
Co-authored-by: oandregal
Co-authored-by: ntsekouras
---
docs/reference-guides/slotfills/README.md | 9 ++---
.../src/components/post-actions/index.js | 36 +++++++++----------
.../src/components/post-card-panel/index.js | 13 +++++--
.../src/components/sidebar/post-summary.js | 7 +---
4 files changed, 30 insertions(+), 35 deletions(-)
diff --git a/docs/reference-guides/slotfills/README.md b/docs/reference-guides/slotfills/README.md
index 8b56ed4ce98b41..5ae68cdb5cb071 100644
--- a/docs/reference-guides/slotfills/README.md
+++ b/docs/reference-guides/slotfills/README.md
@@ -70,11 +70,10 @@ export default function PostSummary( { onActionPerformed } ) {
const { isRemovedPostStatusPanel } = useSelect( ( select ) => {
// We use isEditorPanelRemoved to hide the panel if it was programmatically removed. We do
// not use isEditorPanelEnabled since this panel should not be disabled through the UI.
- const { isEditorPanelRemoved, getCurrentPostType } =
+ const { isEditorPanelRemoved } =
select( editorStore );
return {
isRemovedPostStatusPanel: isEditorPanelRemoved( PANEL_NAME ),
- postType: getCurrentPostType(),
};
}, [] );
@@ -85,11 +84,7 @@ export default function PostSummary( { onActionPerformed } ) {
<>
- }
+ onActionPerformed={ onActionPerformed }
/>
diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js
index 5b023956178938..9cc594233c8814 100644
--- a/packages/editor/src/components/post-actions/index.js
+++ b/packages/editor/src/components/post-actions/index.js
@@ -17,7 +17,6 @@ import { store as coreStore } from '@wordpress/core-data';
*/
import { unlock } from '../../lock-unlock';
import { usePostActions } from './actions';
-import { store as editorStore } from '../../store';
const {
DropdownMenuV2: DropdownMenu,
@@ -27,25 +26,23 @@ const {
kebabCase,
} = unlock( componentsPrivateApis );
-export default function PostActions( { onActionPerformed, buttonProps } ) {
+export default function PostActions( { postType, postId, onActionPerformed } ) {
const [ isActionsMenuOpen, setIsActionsMenuOpen ] = useState( false );
- const { item, permissions, postType } = useSelect( ( select ) => {
- const { getCurrentPostType, getCurrentPostId } = select( editorStore );
- const { getEditedEntityRecord, getEntityRecordPermissions } = unlock(
- select( coreStore )
- );
- const _postType = getCurrentPostType();
- const _id = getCurrentPostId();
- return {
- item: getEditedEntityRecord( 'postType', _postType, _id ),
- permissions: getEntityRecordPermissions(
- 'postType',
- _postType,
- _id
- ),
- postType: _postType,
- };
- }, [] );
+ const { item, permissions } = useSelect(
+ ( select ) => {
+ const { getEditedEntityRecord, getEntityRecordPermissions } =
+ unlock( select( coreStore ) );
+ return {
+ item: getEditedEntityRecord( 'postType', postType, postId ),
+ permissions: getEntityRecordPermissions(
+ 'postType',
+ postType,
+ postId
+ ),
+ };
+ },
+ [ postId, postType ]
+ );
const itemWithPermissions = useMemo( () => {
return {
...item,
@@ -76,7 +73,6 @@ export default function PostActions( { onActionPerformed, buttonProps } ) {
onClick={ () =>
setIsActionsMenuOpen( ! isActionsMenuOpen )
}
- { ...buttonProps }
/>
}
onOpenChange={ setIsActionsMenuOpen }
diff --git a/packages/editor/src/components/post-card-panel/index.js b/packages/editor/src/components/post-card-panel/index.js
index 5ac88b3dbc7f94..ed13af9b55a4aa 100644
--- a/packages/editor/src/components/post-card-panel/index.js
+++ b/packages/editor/src/components/post-card-panel/index.js
@@ -26,8 +26,13 @@ import {
GLOBAL_POST_TYPES,
} from '../../store/constants';
import { unlock } from '../../lock-unlock';
+import PostActions from '../post-actions';
-export default function PostCardPanel( { postType, postId, actions } ) {
+export default function PostCardPanel( {
+ postType,
+ postId,
+ onActionPerformed,
+} ) {
const { isFrontPage, isPostsPage, title, icon, isSync } = useSelect(
( select ) => {
const { __experimentalGetTemplateInfo } = select( editorStore );
@@ -105,7 +110,11 @@ export default function PostCardPanel( { postType, postId, actions } ) {
) }
- { actions }
+
);
diff --git a/packages/editor/src/components/sidebar/post-summary.js b/packages/editor/src/components/sidebar/post-summary.js
index 573a59de827188..b19848f2247063 100644
--- a/packages/editor/src/components/sidebar/post-summary.js
+++ b/packages/editor/src/components/sidebar/post-summary.js
@@ -8,7 +8,6 @@ import { useSelect } from '@wordpress/data';
* Internal dependencies
*/
import PluginPostStatusInfo from '../plugin-post-status-info';
-import PostActions from '../post-actions';
import PostAuthorPanel from '../post-author/panel';
import PostCardPanel from '../post-card-panel';
import PostContentInformation from '../post-content-information';
@@ -63,11 +62,7 @@ export default function PostSummary( { onActionPerformed } ) {
- }
+ onActionPerformed={ onActionPerformed }
/>
From e44bc77a252e81a549652e6ab480d62cb8184d5e Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Fri, 9 Aug 2024 14:34:50 +0200
Subject: [PATCH 027/126] DataViews Quick Edit: Rely on the global save flow
instead of a custom save button (#64389)
Co-authored-by: youknowriad
Co-authored-by: oandregal
---
packages/dataviews/CHANGELOG.md | 1 +
.../dataform/stories/index.story.tsx | 7 +-
.../dataviews/src/field-types/datetime.tsx | 6 +-
.../dataviews/src/field-types/integer.tsx | 5 +-
packages/dataviews/src/field-types/text.tsx | 5 +-
packages/dataviews/src/types.ts | 11 +--
.../src/components/post-edit/index.js | 73 ++++++-------------
.../src/components/post-actions/actions.js | 7 +-
.../src/dataviews/actions/reorder-page.tsx | 7 +-
9 files changed, 50 insertions(+), 72 deletions(-)
diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md
index 239a69651a1f27..b1cb4504e72fad 100644
--- a/packages/dataviews/CHANGELOG.md
+++ b/packages/dataviews/CHANGELOG.md
@@ -16,6 +16,7 @@
- `setSelection` prop has been removed. Please use `onChangeSelection` instead.
- `header` field property has been renamed to `label`.
- `DataForm`'s `visibleFields` prop has been renamed to `fields`.
+- `DataForm`'s `onChange` prop has been update to receive as argument only the fields that have changed.
### New features
diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx
index 7f3c5ff879b72a..7147b9c2342638 100644
--- a/packages/dataviews/src/components/dataform/stories/index.story.tsx
+++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx
@@ -109,7 +109,12 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => {
...form,
type,
} }
- onChange={ setPost }
+ onChange={ ( edits ) =>
+ setPost( ( prev ) => ( {
+ ...prev,
+ ...edits,
+ } ) )
+ }
/>
);
};
diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx
index 3861a0db635d7b..c6b69048efe13c 100644
--- a/packages/dataviews/src/field-types/datetime.tsx
+++ b/packages/dataviews/src/field-types/datetime.tsx
@@ -41,11 +41,7 @@ function Edit< Item >( {
const value = field.getValue( { item: data } );
const onChangeControl = useCallback(
- ( newValue: string | null ) =>
- onChange( ( prevItem: Item ) => ( {
- ...prevItem,
- [ id ]: newValue,
- } ) ),
+ ( newValue: string | null ) => onChange( { [ id ]: newValue } ),
[ id, onChange ]
);
diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx
index bd4956493232df..38570ea6fec1a5 100644
--- a/packages/dataviews/src/field-types/integer.tsx
+++ b/packages/dataviews/src/field-types/integer.tsx
@@ -51,10 +51,9 @@ function Edit< Item >( {
const value = field.getValue( { item: data } ) ?? '';
const onChangeControl = useCallback(
( newValue: string | undefined ) =>
- onChange( ( prevItem: Item ) => ( {
- ...prevItem,
+ onChange( {
[ id ]: Number( newValue ),
- } ) ),
+ } ),
[ id, onChange ]
);
diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx
index c6efb85f6f446b..5364017c629b00 100644
--- a/packages/dataviews/src/field-types/text.tsx
+++ b/packages/dataviews/src/field-types/text.tsx
@@ -42,10 +42,9 @@ function Edit< Item >( {
const onChangeControl = useCallback(
( newValue: string ) =>
- onChange( ( prevItem: Item ) => ( {
- ...prevItem,
+ onChange( {
[ id ]: newValue,
- } ) ),
+ } ),
[ id, onChange ]
);
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index d8a5ee8f68ecef..34e74eabd7c7d8 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -1,12 +1,7 @@
/**
* External dependencies
*/
-import type {
- ReactElement,
- ComponentType,
- Dispatch,
- SetStateAction,
-} from 'react';
+import type { ReactElement, ComponentType } from 'react';
/**
* Internal dependencies
@@ -181,7 +176,7 @@ export type Form = {
export type DataFormControlProps< Item > = {
data: Item;
field: NormalizedField< Item >;
- onChange: Dispatch< SetStateAction< Item > >;
+ onChange: ( value: Record< string, any > ) => void;
hideLabelFromVision?: boolean;
};
@@ -516,5 +511,5 @@ export interface DataFormProps< Item > {
data: Item;
fields: Field< Item >[];
form: Form;
- onChange: Dispatch< SetStateAction< Item > >;
+ onChange: ( value: Record< string, any > ) => void;
}
diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index 0a56fdfe5786b7..80304f16503705 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -7,15 +7,11 @@ import clsx from 'clsx';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import { DataForm, isItemValid } from '@wordpress/dataviews';
-import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
+import { DataForm } from '@wordpress/dataviews';
+import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';
-import {
- Button,
- FlexItem,
- __experimentalVStack as VStack,
-} from '@wordpress/components';
-import { useState, useMemo } from '@wordpress/element';
+import { __experimentalVStack as VStack } from '@wordpress/components';
+import { useState, useMemo, useEffect } from '@wordpress/element';
import { privateApis as editorPrivateApis } from '@wordpress/editor';
/**
@@ -29,14 +25,12 @@ const { PostCardPanel } = unlock( editorPrivateApis );
function PostEditForm( { postType, postId } ) {
const ids = useMemo( () => postId.split( ',' ), [ postId ] );
- const { initialEdits } = useSelect(
+ const { record } = useSelect(
( select ) => {
- if ( ids.length !== 1 ) {
- }
return {
- initialEdits:
+ record:
ids.length === 1
- ? select( coreDataStore ).getEntityRecord(
+ ? select( coreDataStore ).getEditedEntityRecord(
'postType',
postType,
ids[ 0 ]
@@ -46,60 +40,39 @@ function PostEditForm( { postType, postId } ) {
},
[ postType, ids ]
);
- const registry = useRegistry();
- const { saveEntityRecord } = useDispatch( coreDataStore );
+ const [ multiEdits, setMultiEdits ] = useState( {} );
+ const { editEntityRecord } = useDispatch( coreDataStore );
const { fields } = usePostFields();
const form = {
type: 'panel',
fields: [ 'title', 'author', 'date', 'comment_status' ],
};
- const [ edits, setEdits ] = useState( initialEdits );
- const itemWithEdits = useMemo( () => {
- return {
- ...initialEdits,
- ...edits,
- };
- }, [ initialEdits, edits ] );
- const onSubmit = async ( event ) => {
- event.preventDefault();
-
- if ( ! isItemValid( itemWithEdits, fields, form ) ) {
- return;
- }
-
- const { getEntityRecord } = registry.resolveSelect( coreDataStore );
+ const onChange = ( edits ) => {
for ( const id of ids ) {
- const item = await getEntityRecord( 'postType', postType, id );
- saveEntityRecord( 'postType', postType, {
- ...item,
- ...edits,
- } );
+ editEntityRecord( 'postType', postType, id, edits );
+ if ( ids.length > 1 ) {
+ setMultiEdits( ( prev ) => ( {
+ ...prev,
+ ...edits,
+ } ) );
+ }
}
};
+ useEffect( () => {
+ setMultiEdits( {} );
+ }, [ ids ] );
- const isUpdateDisabled = ! isItemValid( itemWithEdits, fields, form );
return (
-
+
{ ids.length === 1 && (
) }
-
-
- { __( 'Update' ) }
-
-
);
}
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 5e727376a92258..ff5cd7ddbb5452 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -234,7 +234,12 @@ const useDuplicatePostAction = ( postType ) => {
data={ item }
fields={ fields }
form={ formDuplicateAction }
- onChange={ setItem }
+ onChange={ ( changes ) =>
+ setItem( {
+ ...item,
+ ...changes,
+ } )
+ }
/>
+ setItem( {
+ ...item,
+ ...changes,
+ } )
+ }
/>
Date: Fri, 9 Aug 2024 17:50:59 +0200
Subject: [PATCH 028/126] Fix: Remove unnecessary className. (#64403)
Co-authored-by: jorgefilipecosta
---
.../dataviews/src/components/dataviews-view-config/index.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx
index 28e151079008eb..0aae714c5ce27a 100644
--- a/packages/dataviews/src/components/dataviews-view-config/index.tsx
+++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx
@@ -239,7 +239,6 @@ function FieldControl() {
{ field.label }
onChangeView( {
From b4ba75fd00030a012c064974181d41ee30f81b91 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Fri, 9 Aug 2024 18:31:48 +0200
Subject: [PATCH 029/126] Composite: improve Storybook examples and clean up
prop docs (#64397)
* More clear tranform function comments
* Add interactive controls
* Add simpler default Storybook example
* Improve `activeId`'s prop description
* Add Groups example
* CHANGELOG
* Removed actions config in Storybook
* Better composite description
* Remove direct references to Ariakit's docs in JSDocs and README
* Add import from `@wordpress/components` in all code snippets
* useCompositeStore: update prop docs by using first-party docs
Instead of using Ariakit's definitions and descriptions, we use our own
version, which a copy or Ariakit's without any references to Ariakit, its
examples, or any other props that we don't expose.
* useCompositeStore: set explicit default values
Along the same fashion as the previous commit, setting explicit default
values will give us more control when propagating ariakit changes, and
will allows to stay true to our first-part props docs.
* Remove unnecessary space
* Provide first-party prop descriptions also for other composite components
* Add default value for store props to avoid errors while destructuring
---
Co-authored-by: ciampo
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/composite/README.md | 62 ++++-
packages/components/src/composite/index.tsx | 49 +++-
.../src/composite/stories/index.story.tsx | 117 ++++++++--
.../src/composite/stories/utils.tsx | 18 +-
packages/components/src/composite/types.ts | 211 +++++++++++++++---
6 files changed, 384 insertions(+), 74 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 91c5a8c3bd012d..dd8a7a720258f2 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -8,6 +8,7 @@
### Enhancements
+- `Composite`: improve Storybook examples and add interactive controls ([#64397](https://github.com/WordPress/gutenberg/pull/64397)).
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
## 28.5.0 (2024-08-07)
diff --git a/packages/components/src/composite/README.md b/packages/components/src/composite/README.md
index 59953f1273a054..384fa46b1a9217 100644
--- a/packages/components/src/composite/README.md
+++ b/packages/components/src/composite/README.md
@@ -2,11 +2,11 @@
`Composite` provides a single tab stop on the page and allows navigation through the focusable descendants with arrow keys. This abstract component is based on the [WAI-ARIA Composite Role](https://w3c.github.io/aria/#composite).
-See the [Ariakit docs for the `Composite` component](https://ariakit.org/components/composite).
-
## Usage
```jsx
+import { Composite, useCompositeStore } from '@wordpress/components';
+
const store = useCompositeStore();
@@ -27,7 +27,10 @@ Creates a composite store.
##### `activeId`: `string | null`
-The current active item id. The active item is the element within the composite widget that has either DOM or virtual focus.
+The current active item `id`. The active item is the element within the composite widget that has either DOM or virtual focus (in case the `virtualFocus` prop is enabled).
+
+- `null` represents the base composite element (the one with a [composite role](https://w3c.github.io/aria/#composite)). Users will be able to navigate out of it using arrow keys.
+- If `activeId` is initially set to `null`, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
- Required: no
@@ -39,7 +42,7 @@ The composite item id that should be active by default when the composite widget
##### `setActiveId`: `((activeId: string | null | undefined) => void)`
-A callback that gets called when the activeId state changes.
+A callback that gets called when the `activeId` state changes.
- Required: no
@@ -47,40 +50,79 @@ A callback that gets called when the activeId state changes.
Determines how the focus behaves when the user reaches the end of the composite widget.
+On one-dimensional composite widgets:
+
+- `true` loops from the last item to the first item and vice-versa.
+- `horizontal` loops only if `orientation` is `horizontal` or not set.
+- `vertical` loops only if `orientation` is `vertical` or not set.
+- If `activeId` is initially set to `null`, the composite element will be focused in between the last and first items.
+
+On two-dimensional composite widgets (ie. when using `CompositeRow`):
+
+- `true` loops from the last row/column item to the first item in the same row/column and vice-versa. If it's the last item in the last row, it moves to the first item in the first row and vice-versa.
+- `horizontal` loops only from the last row item to the first item in the same row.
+- `vertical` loops only from the last column item to the first item in the column row.
+- If `activeId` is initially set to `null`, vertical loop will have no effect as moving down from the last row or up from the first row will focus on the composite element.
+- If `focusWrap` matches the value of `focusLoop`, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
+
- Required: no
- Default: `false`
##### `focusShift`: `boolean`
-Works only on two-dimensional composite widgets. If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.
+**Works only on two-dimensional composite widgets**.
+
+If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.
- Required: no
- Default: `false`
##### `focusWrap`: `boolean`
-Works only on two-dimensional composite widgets. If enabled, moving to the next item from the last one in a row or column will focus on the first item in the next row or column and vice-versa.
+**Works only on two-dimensional composite widgets**.
+
+If enabled, moving to the next item from the last one in a row or column
+will focus on the first item in the next row or column and vice-versa.
+
+- `true` wraps between rows and columns.
+- `horizontal` wraps only between rows.
+- `vertical` wraps only between columns.
+- If `focusLoop` matches the value of `focusWrap`, it'll wrap between the
+ last item in the last row or column and the first item in the first row or
+ column and vice-versa.
- Required: no
- Default: `false`
##### `virtualFocus`: `boolean`
-If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite element while its items receive virtual focus. In both scenarios, the item in focus will carry the data-active-item attribute.
+If enabled, the composite element will act as an [`aria-activedescendant`](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant)
+container instead of [roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex). DOM focus will remain on the composite element while its items receive
+virtual focus.
+
+In both scenarios, the item in focus will carry the `data-active-item` attribute.
- Required: no
- Default: `false`
##### `orientation`: `'horizontal' | 'vertical' | 'both'`
-Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus. It doesn't have any effect on two-dimensional composites.
+Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the `orientation` value determines which arrow keys can be used to move focus:
+
+- `both`: all arrow keys work.
+- `horizontal`: only left and right arrow keys work.
+- `vertical`: only up and down arrow keys work.
+
+It doesn't have any effect on two-dimensional composites.
- Required: no
-- Default: `'both'`
+- Default: `both`
##### `rtl`: `boolean`
-Determines how the next and previous functions will behave. If rtl is set to true, they will be inverted. This only affects the composite widget behavior. You still need to set dir=`rtl` on HTML/CSS.
+Determines how the `store`'s `next` and `previous` functions will behave. If `rtl` is set to `true`, they will be inverted.
+
+This only affects the composite widget behavior. You still need to set `dir="rtl"` on HTML/CSS.
- Required: no
- Default: `false`
diff --git a/packages/components/src/composite/index.tsx b/packages/components/src/composite/index.tsx
index 9496bdb9e98664..73df75272054c7 100644
--- a/packages/components/src/composite/index.tsx
+++ b/packages/components/src/composite/index.tsx
@@ -33,10 +33,11 @@ import type {
/**
* Creates a composite store.
- * @param props
- * @see https://ariakit.org/reference/use-composite-store
+ *
* @example
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
* Item
@@ -45,8 +46,24 @@ import type {
*
* ```
*/
-export function useCompositeStore( props: CompositeStoreProps ) {
- return Ariakit.useCompositeStore( props );
+export function useCompositeStore( {
+ focusLoop = false,
+ focusWrap = false,
+ focusShift = false,
+ virtualFocus = false,
+ orientation = 'both',
+ rtl = false,
+ ...props
+}: CompositeStoreProps = {} ) {
+ return Ariakit.useCompositeStore( {
+ focusLoop,
+ focusWrap,
+ focusShift,
+ virtualFocus,
+ orientation,
+ rtl,
+ ...props,
+ } );
}
const Group = forwardRef<
@@ -82,10 +99,14 @@ const Row = forwardRef<
Row.displayName = 'Composite.Row';
/**
- * Renders a composite widget.
- * @see https://ariakit.org/reference/composite
+ * Renders a widget based on the WAI-ARIA [`composite`](https://w3c.github.io/aria/#composite)
+ * role, which provides a single tab stop on the page and arrow key navigation
+ * through the focusable descendants.
+ *
* @example
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
* Item 1
@@ -104,9 +125,11 @@ export const Composite = Object.assign(
displayName: 'Composite',
/**
* Renders a group element for composite items.
- * @see https://ariakit.org/reference/composite-group
+ *
* @example
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
*
@@ -122,9 +145,11 @@ export const Composite = Object.assign(
* Renders a label in a composite group. This component must be wrapped with
* `Composite.Group` so the `aria-labelledby` prop is properly set on the
* composite group element.
- * @see https://ariakit.org/reference/composite-group-label
+ *
* @example
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
*
@@ -138,9 +163,11 @@ export const Composite = Object.assign(
GroupLabel,
/**
* Renders a composite item.
- * @see https://ariakit.org/reference/composite-item
+ *
* @example
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
* Item 1
@@ -154,9 +181,11 @@ export const Composite = Object.assign(
* Renders a composite row. Wrapping `Composite.Item` elements within
* `Composite.Row` will create a two-dimensional composite widget, such as a
* grid.
- * @see https://ariakit.org/reference/composite-row
+ *
* @example
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
*
diff --git a/packages/components/src/composite/stories/index.story.tsx b/packages/components/src/composite/stories/index.story.tsx
index b143c1f7db05f7..280ed7b70546a4 100644
--- a/packages/components/src/composite/stories/index.story.tsx
+++ b/packages/components/src/composite/stories/index.story.tsx
@@ -29,7 +29,25 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
'Composite.Item': Composite.Item,
},
+ argTypes: {
+ activeId: { control: 'text' },
+ defaultActiveId: { control: 'text' },
+ setActiveId: { control: { type: null } },
+ focusLoop: {
+ control: 'select',
+ options: [ true, false, 'horizontal', 'vertical', 'both' ],
+ },
+ focusShift: { control: 'boolean' },
+ focusWrap: { control: 'boolean' },
+ virtualFocus: { control: 'boolean' },
+ rtl: { control: 'boolean' },
+ orientation: {
+ control: 'select',
+ options: [ 'horizontal', 'vertical', 'both' ],
+ },
+ },
parameters: {
+ controls: { expanded: true },
docs: {
canvas: { sourceState: 'shown' },
source: { transform },
@@ -56,8 +74,9 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
useCompositeStore: {
activeId: {
name: 'activeId',
- description:
- 'The current active item id. The active item is the element within the composite widget that has either DOM or virtual focus.',
+ description: `The current active item \`id\`. The active item is the element within the composite widget that has either DOM or virtual focus (in case the \`virtualFocus\` prop is enabled).
+- \`null\` represents the base composite element (the one with a [composite role](https://w3c.github.io/aria/#composite)). Users will be able to navigate out of it using arrow keys.
+- If \`activeId\` is initially set to \`null\`, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.`,
table: { type: { summary: 'string | null' } },
},
defaultActiveId: {
@@ -69,7 +88,7 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
setActiveId: {
name: 'setActiveId',
description:
- 'A callback that gets called when the activeId state changes.',
+ 'A callback that gets called when the `activeId` state changes.',
table: {
type: {
summary:
@@ -79,8 +98,20 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
},
focusLoop: {
name: 'focusLoop',
- description:
- 'Determines how the focus behaves when the user reaches the end of the composite widget.',
+ description: `On one-dimensional composite widgets:
+
+- \`true\` loops from the last item to the first item and vice-versa.
+- \`horizontal\` loops only if \`orientation\` is \`horizontal\` or not set.
+- \`vertical\` loops only if \`orientation\` is \`vertical\` or not set.
+- If \`activeId\` is initially set to \`null\`, the composite element will be focused in between the last and first items.
+
+On two-dimensional composite widgets (ie. when using \`CompositeRow\`):
+
+- \`true\` loops from the last row/column item to the first item in the same row/column and vice-versa. If it's the last item in the last row, it moves to the first item in the first row and vice-versa.
+- \`horizontal\` loops only from the last row item to the first item in the same row.
+- \`vertical\` loops only from the last column item to the first item in the column row.
+- If \`activeId\` is initially set to \`null\`, vertical loop will have no effect as moving down from the last row or up from the first row will focus on the composite element.
+- If \`focusWrap\` matches the value of \`focusLoop\`, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.`,
table: {
defaultValue: {
summary: 'false',
@@ -93,8 +124,9 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
},
focusShift: {
name: 'focusShift',
- description:
- "Works only on two-dimensional composite widgets. If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.",
+ description: `**Works only on two-dimensional composite widgets**.
+
+If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.`,
table: {
defaultValue: {
summary: 'false',
@@ -106,8 +138,17 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
},
focusWrap: {
name: 'focusWrap',
- description:
- 'Works only on two-dimensional composite widgets. If enabled, moving to the next item from the last one in a row or column will focus on the first item in the next row or column and vice-versa.',
+ description: `**Works only on two-dimensional composite widgets**.
+
+If enabled, moving to the next item from the last one in a row or column
+will focus on the first item in the next row or column and vice-versa.
+
+- \`true\` wraps between rows and columns.
+- \`horizontal\` wraps only between rows.
+- \`vertical\` wraps only between columns.
+- If \`focusLoop\` matches the value of \`focusWrap\`, it'll wrap between the
+ last item in the last row or column and the first item in the first row or
+ column and vice-versa.`,
table: {
defaultValue: {
summary: 'false',
@@ -119,8 +160,11 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
},
virtualFocus: {
name: 'virtualFocus',
- description:
- 'If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite element while its items receive virtual focus. In both scenarios, the item in focus will carry the data-active-item attribute.',
+ description: `If enabled, the composite element will act as an [\`aria-activedescendant\`](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant)
+container instead of [roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex). DOM focus will remain on the composite element while its items receive
+virtual focus.
+
+In both scenarios, the item in focus will carry the \`data-active-item\` attribute.`,
table: {
defaultValue: {
summary: 'false',
@@ -132,8 +176,13 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
},
orientation: {
name: 'orientation',
- description:
- "Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus. It doesn't have any effect on two-dimensional composites.",
+ description: `Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the \`orientation\` value determines which arrow keys can be used to move focus:
+
+- \`both\`: all arrow keys work.
+- \`horizontal\`: only left and right arrow keys work.
+- \`vertical\`: only up and down arrow keys work.
+
+It doesn't have any effect on two-dimensional composites.`,
table: {
defaultValue: {
summary: "'both'",
@@ -146,8 +195,9 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
},
rtl: {
name: 'rtl',
- description:
- 'Determines how the next and previous functions will behave. If rtl is set to true, they will be inverted. This only affects the composite widget behavior. You still need to set dir="rtl" on HTML/CSS.',
+ description: `Determines how the \`store\`'s \`next\` and \`previous\` functions will behave. If \`rtl\` is set to \`true\`, they will be inverted.
+
+This only affects the composite widget behavior. You still need to set \`dir="rtl"\` on HTML/CSS.`,
table: {
defaultValue: {
summary: 'false',
@@ -196,6 +246,43 @@ export const Default: StoryFn< typeof UseCompositeStorePlaceholder > = (
const rtl = isRTL();
const store = useCompositeStore( { rtl, ...storeProps } );
+ return (
+
+ Item one
+ Item two
+ Item three
+
+ );
+};
+
+export const Groups: StoryFn< typeof UseCompositeStorePlaceholder > = (
+ storeProps
+) => {
+ const rtl = isRTL();
+ const store = useCompositeStore( { rtl, ...storeProps } );
+
+ return (
+
+
+ Group one
+ Item 1.1
+ Item 1.2
+
+
+ Group two
+ Item 2.1
+ Item 2.1
+
+
+ );
+};
+
+export const Grid: StoryFn< typeof UseCompositeStorePlaceholder > = (
+ storeProps
+) => {
+ const rtl = isRTL();
+ const store = useCompositeStore( { rtl, ...storeProps } );
+
return (
diff --git a/packages/components/src/composite/stories/utils.tsx b/packages/components/src/composite/stories/utils.tsx
index 425fb9a905bcff..f2f197877ff76d 100644
--- a/packages/components/src/composite/stories/utils.tsx
+++ b/packages/components/src/composite/stories/utils.tsx
@@ -9,9 +9,13 @@ import type { StoryContext } from '@storybook/react';
import type { CompositeStoreProps } from '../types';
/**
- * Renders a composite widget.
+ * Renders a widget based on the WAI-ARIA [`composite`](https://w3c.github.io/aria/#composite)
+ * role, which provides a single tab stop on the page and arrow key navigation
+ * through the focusable descendants.
*
* ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
* const store = useCompositeStore();
*
* Item 1
@@ -33,17 +37,17 @@ export function UseCompositeStorePlaceholder( props: CompositeStoreProps ) {
}
UseCompositeStorePlaceholder.displayName = 'useCompositeStore';
+// The output generated by Storybook for these components is
+// messy, so we apply this transform to make it more useful
+// for anyone reading the docs.
export function transform( code: string, context: StoryContext ) {
- // The output generated by Storybook for these components is
- // messy, so we apply this transform to make it more useful
- // for anyone reading the docs.
- const config = ` ${ JSON.stringify( context.args, null, 2 ) } `;
- const state = config.replace( ' {} ', '' );
+ const storeConfig = ` ${ JSON.stringify( context.args, null, 2 ) } `;
+ const formattedStoreConfig = storeConfig.replace( ' {} ', '' );
return [
// Include a setup line, showing how to make use of
// `useCompositeStore` to convert store options into
// a composite store prop.
- `const store = useCompositeStore(${ state });`,
+ `const store = useCompositeStore(${ formattedStoreConfig });`,
'',
'return (',
' ' +
diff --git a/packages/components/src/composite/types.ts b/packages/components/src/composite/types.ts
index 438d1caaa94f8a..37709133915d6c 100644
--- a/packages/components/src/composite/types.ts
+++ b/packages/components/src/composite/types.ts
@@ -3,45 +3,192 @@
*/
import type * as Ariakit from '@ariakit/react';
-export type CompositeStoreProps = Pick<
- Ariakit.CompositeStoreProps,
- | 'activeId'
- | 'defaultActiveId'
- | 'setActiveId'
- | 'focusLoop'
- | 'focusShift'
- | 'focusWrap'
- | 'virtualFocus'
- | 'orientation'
- | 'rtl'
->;
+export type CompositeStoreProps = {
+ /**
+ * The current active item `id`. The active item is the element within the
+ * composite widget that has either DOM or virtual focus (in case
+ * the `virtualFocus` prop is enabled).
+ * - `null` represents the base composite element (the one with a [composite
+ * role](https://w3c.github.io/aria/#composite)). Users will be able to
+ * navigate out of it using arrow keys.
+ * - If `activeId` is initially set to `null`, the base composite element
+ * itself will have focus and users will be able to navigate to it using
+ * arrow keys.
+ */
+ activeId?: Ariakit.CompositeStoreProps[ 'activeId' ];
+ /**
+ * The composite item id that should be active by default when the composite
+ * widget is rendered. If `null`, the composite element itself will have focus
+ * and users will be able to navigate to it using arrow keys. If `undefined`,
+ * the first enabled item will be focused.
+ */
+ defaultActiveId?: Ariakit.CompositeStoreProps[ 'defaultActiveId' ];
+ /**
+ * A callback that gets called when the `activeId` state changes.
+ */
+ setActiveId?: Ariakit.CompositeStoreProps[ 'setActiveId' ];
+ /**
+ * Determines how the focus behaves when the user reaches the end of the
+ * composite widget.
+ *
+ * On one-dimensional composite widgets:
+ * - `true` loops from the last item to the first item and vice-versa.
+ * - `horizontal` loops only if `orientation` is `horizontal` or not set.
+ * - `vertical` loops only if `orientation` is `vertical` or not set.
+ * - If `activeId` is initially set to `null`, the composite element will
+ * be focused in between the last and first items.
+ *
+ * On two-dimensional composite widgets (ie. when using `CompositeRow`):
+ * - `true` loops from the last row/column item to the first item in the same
+ * row/column and vice-versa. If it's the last item in the last row, it
+ * moves to the first item in the first row and vice-versa.
+ * - `horizontal` loops only from the last row item to the first item in the
+ * same row.
+ * - `vertical` loops only from the last column item to the first item in the
+ * column row.
+ * - If `activeId` is initially set to `null`, vertical loop will have no
+ * effect as moving down from the last row or up from the first row will
+ * focus on the composite element.
+ * - If `focusWrap` matches the value of `focusLoop`, it'll wrap between the
+ * last item in the last row or column and the first item in the first row or
+ * column and vice-versa.
+ *
+ * @default false
+ */
+ focusLoop?: Ariakit.CompositeStoreProps[ 'focusLoop' ];
+ /**
+ * **Works only on two-dimensional composite widgets**.
+ *
+ * If enabled, moving to the next item from the last one in a row or column
+ * will focus on the first item in the next row or column and vice-versa.
+ * - `true` wraps between rows and columns.
+ * - `horizontal` wraps only between rows.
+ * - `vertical` wraps only between columns.
+ * - If `focusLoop` matches the value of `focusWrap`, it'll wrap between the
+ * last item in the last row or column and the first item in the first row or
+ * column and vice-versa.
+ *
+ * @default false
+ */
+ focusWrap?: Ariakit.CompositeStoreProps[ 'focusWrap' ];
+ /**
+ * **Works only on two-dimensional composite widgets**.
+ *
+ * If enabled, moving up or down when there's no next item or when the next
+ * item is disabled will shift to the item right before it.
+ *
+ * @default false
+ */
+ focusShift?: Ariakit.CompositeStoreProps[ 'focusShift' ];
+ /**
+ * If enabled, the composite element will act as an
+ * [`aria-activedescendant`](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant)
+ * container instead of [roving
+ * tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex).
+ * DOM focus will remain on the composite element while its items receive
+ * virtual focus.
+ *
+ * In both scenarios, the item in focus will carry the `data-active-item`
+ * attribute.
+ *
+ * @default false
+ */
+ virtualFocus?: Ariakit.CompositeStoreProps[ 'virtualFocus' ];
+ /**
+ * Defines the orientation of the composite widget. If the composite has a
+ * single row or column (one-dimensional), the `orientation` value determines
+ * which arrow keys can be used to move focus:
+ * - `both`: all arrow keys work.
+ * - `horizontal`: only left and right arrow keys work.
+ * - `vertical`: only up and down arrow keys work.
+ *
+ * It doesn't have any effect on two-dimensional composites.
+ *
+ * @default "both"
+ */
+ orientation?: Ariakit.CompositeStoreProps[ 'orientation' ];
+ /**
+ * Determines how the `store`'s `next` and `previous` functions will behave.
+ * If `rtl` is set to `true`, they will be inverted.
+ *
+ * This only affects the composite widget behavior. You still need to set
+ * `dir="rtl"` on HTML/CSS.
+ *
+ * @default false
+ */
+ rtl?: Ariakit.CompositeStoreProps[ 'rtl' ];
+};
-export type CompositeProps = Pick<
- Ariakit.CompositeProps,
- 'render' | 'children'
-> & {
+export type CompositeProps = {
/**
* Object returned by the `useCompositeStore` hook.
*/
store: Ariakit.CompositeStore;
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeProps[ 'children' ];
};
-export type CompositeGroupProps = Pick<
- Ariakit.CompositeGroupProps,
- 'render' | 'children'
->;
+export type CompositeGroupProps = {
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeGroupProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeGroupProps[ 'children' ];
+};
-export type CompositeGroupLabelProps = Pick<
- Ariakit.CompositeGroupLabelProps,
- 'render' | 'children'
->;
+export type CompositeGroupLabelProps = {
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeGroupLabelProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeGroupLabelProps[ 'children' ];
+};
-export type CompositeItemProps = Pick<
- Ariakit.CompositeItemProps,
- 'render' | 'children'
->;
+export type CompositeItemProps = {
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeItemProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeItemProps[ 'children' ];
+};
-export type CompositeRowProps = Pick<
- Ariakit.CompositeRowProps,
- 'render' | 'children'
->;
+export type CompositeRowProps = {
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeRowProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeRowProps[ 'children' ];
+};
From 46a026bde390382bbebb283dca25d2dba91b2b67 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Fri, 9 Aug 2024 19:15:18 +0200
Subject: [PATCH 030/126] Composite: add Hover and Typeahead subcomponents
(#64399)
* Composite: add Hover and Typeahead subcomponents
* Add hover example + styles to highlight active item
* Add Composite.Hover props docs
* Add Typeahead docs and Storybook example
* CHANGELOG
* Remove the `focusOnHover` and `blurOnHoverEnd` props.
* Remove ariakit references
* Add import statements to code examples
---
Co-authored-by: ciampo
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/composite/README.md | 32 +++++++
packages/components/src/composite/index.tsx | 56 ++++++++++++
.../src/composite/stories/index.story.tsx | 91 +++++++++++++++++++
packages/components/src/composite/types.ts | 28 ++++++
5 files changed, 208 insertions(+)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index dd8a7a720258f2..96bf76815e71dd 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -5,6 +5,7 @@
### New Features
- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).
+- `Composite`: add `Hover` and `Typeahead` subcomponents ([#64399](https://github.com/WordPress/gutenberg/pull/64399)).
### Enhancements
diff --git a/packages/components/src/composite/README.md b/packages/components/src/composite/README.md
index 384fa46b1a9217..7bd12d0cabfa0c 100644
--- a/packages/components/src/composite/README.md
+++ b/packages/components/src/composite/README.md
@@ -216,3 +216,35 @@ Allows the component to be rendered as a different HTML element or React compone
The contents of the component.
- Required: no
+
+### `Composite.Hover`
+
+Renders an element in a composite widget that receives focus on mouse move and loses focus to the composite base element on mouse leave. This should be combined with the `Composite.Item` component.
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
+
+### `Composite.Typeahead`
+
+Renders a component that adds typeahead functionality to composite components. Hitting printable character keys will move focus to the next composite item that begins with the input characters.
+
+##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
+
+Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
+
+- Required: no
+
+##### `children`: `React.ReactNode`
+
+The contents of the component.
+
+- Required: no
diff --git a/packages/components/src/composite/index.tsx b/packages/components/src/composite/index.tsx
index 73df75272054c7..4e87b9a55fa5bb 100644
--- a/packages/components/src/composite/index.tsx
+++ b/packages/components/src/composite/index.tsx
@@ -29,6 +29,8 @@ import type {
CompositeGroupLabelProps,
CompositeItemProps,
CompositeRowProps,
+ CompositeHoverProps,
+ CompositeTypeaheadProps,
} from './types';
/**
@@ -98,6 +100,22 @@ const Row = forwardRef<
} );
Row.displayName = 'Composite.Row';
+const Hover = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< CompositeHoverProps, 'div', false >
+>( function CompositeHover( props, ref ) {
+ return ;
+} );
+Hover.displayName = 'Composite.Hover';
+
+const Typeahead = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< CompositeTypeaheadProps, 'div', false >
+>( function CompositeTypeahead( props, ref ) {
+ return ;
+} );
+Typeahead.displayName = 'Composite.Typeahead';
+
/**
* Renders a widget based on the WAI-ARIA [`composite`](https://w3c.github.io/aria/#composite)
* role, which provides a single tab stop on the page and arrow key navigation
@@ -202,5 +220,43 @@ export const Composite = Object.assign(
* ```
*/
Row,
+ /**
+ * Renders an element in a composite widget that receives focus on mouse move
+ * and loses focus to the composite base element on mouse leave. This should
+ * be combined with the `Composite.Item` component.
+ *
+ * @example
+ * ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
+ * const store = useCompositeStore();
+ *
+ * }>
+ * Item 1
+ *
+ * }>
+ * Item 2
+ *
+ *
+ * ```
+ */
+ Hover,
+ /**
+ * Renders a component that adds typeahead functionality to composite
+ * components. Hitting printable character keys will move focus to the next
+ * composite item that begins with the input characters.
+ *
+ * @example
+ * ```jsx
+ * import { Composite, useCompositeStore } from '@wordpress/components';
+ *
+ * const store = useCompositeStore();
+ * }>
+ * Item 1
+ * Item 2
+ *
+ * ```
+ */
+ Typeahead,
}
);
diff --git a/packages/components/src/composite/stories/index.story.tsx b/packages/components/src/composite/stories/index.story.tsx
index 280ed7b70546a4..f1be53445f79ad 100644
--- a/packages/components/src/composite/stories/index.story.tsx
+++ b/packages/components/src/composite/stories/index.story.tsx
@@ -28,6 +28,10 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
'Composite.Row': Composite.Row,
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
'Composite.Item': Composite.Item,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ 'Composite.Hover': Composite.Hover,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ 'Composite.Typeahead': Composite.Typeahead,
},
argTypes: {
activeId: { control: 'text' },
@@ -227,6 +231,8 @@ This only affects the composite widget behavior. You still need to set \`dir="rt
'Composite.GroupLabel': commonArgTypes,
'Composite.Row': commonArgTypes,
'Composite.Item': commonArgTypes,
+ 'Composite.Hover': commonArgTypes,
+ 'Composite.Typeahead': commonArgTypes,
};
const name = component.displayName ?? '';
@@ -237,6 +243,41 @@ This only affects the composite widget behavior. You still need to set \`dir="rt
},
},
},
+ decorators: [
+ ( Story ) => {
+ return (
+ <>
+ { /* Visually style the active composite item */ }
+
+
+
+ { /* eslint-disable-next-line no-restricted-syntax */ }
+
Notes
+
+
+ The active composite item is highlighted with a
+ different background color;
+
+
+ A composite item can be the active item even
+ when it doesn't have keyboard focus.
+
+
+
+ >
+ );
+ },
+ ],
};
export default meta;
@@ -303,3 +344,53 @@ export const Grid: StoryFn< typeof UseCompositeStorePlaceholder > = (
);
};
+
+export const Hover: StoryFn< typeof UseCompositeStorePlaceholder > = (
+ storeProps
+) => {
+ const rtl = isRTL();
+ const store = useCompositeStore( { rtl, ...storeProps } );
+
+ return (
+
+ }>
+ Hover item one
+
+ }>
+ Hover item two
+
+ }>
+ Hover item three
+
+
+ );
+};
+Hover.parameters = {
+ docs: {
+ description: {
+ story: 'Elements in the composite widget will receive focus on mouse move and lose focus to the composite base element on mouse leave.',
+ },
+ },
+};
+
+export const Typeahead: StoryFn< typeof UseCompositeStorePlaceholder > = (
+ storeProps
+) => {
+ const rtl = isRTL();
+ const store = useCompositeStore( { rtl, ...storeProps } );
+
+ return (
+ }>
+ Apple
+ Banana
+ Peach
+
+ );
+};
+Typeahead.parameters = {
+ docs: {
+ description: {
+ story: 'When focus in on the composite widget, hitting printable character keys will move focus to the next composite item that begins with the input characters.',
+ },
+ },
+};
diff --git a/packages/components/src/composite/types.ts b/packages/components/src/composite/types.ts
index 37709133915d6c..8bd4b447a83aef 100644
--- a/packages/components/src/composite/types.ts
+++ b/packages/components/src/composite/types.ts
@@ -192,3 +192,31 @@ export type CompositeRowProps = {
*/
children?: Ariakit.CompositeRowProps[ 'children' ];
};
+
+export type CompositeHoverProps = {
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeHoverProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeHoverProps[ 'children' ];
+};
+
+export type CompositeTypeaheadProps = {
+ /**
+ * Allows the component to be rendered as a different HTML element or React
+ * component. The value can be a React element or a function that takes in the
+ * original component props and gives back a React element with the props
+ * merged.
+ */
+ render?: Ariakit.CompositeTypeaheadProps[ 'render' ];
+ /**
+ * The contents of the component.
+ */
+ children?: Ariakit.CompositeTypeaheadProps[ 'children' ];
+};
From 81d5d34ba25cad8faf2ee2ce1c4c4c2d8b6aeb8a Mon Sep 17 00:00:00 2001
From: Damon Cook
Date: Fri, 9 Aug 2024 15:58:34 -0400
Subject: [PATCH 031/126] Docs: Interactivity API: Add wp_interactivity_state()
clarification (#64356)
* Add wp_interactivity_state() clarification
* Update code example for wp_interactivity_state
* Update docs/reference-guides/interactivity-api/api-reference.md
Co-authored-by: Luis Herranz
* Update docs/reference-guides/interactivity-api/api-reference.md
Co-authored-by: Luis Herranz
* Update docs/reference-guides/interactivity-api/api-reference.md
Co-authored-by: Luis Herranz
---------
Co-authored-by: Luis Herranz
---
.../interactivity-api/api-reference.md | 49 ++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)
diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md
index a4b400b8c0276b..29ec67cc17507f 100644
--- a/docs/reference-guides/interactivity-api/api-reference.md
+++ b/docs/reference-guides/interactivity-api/api-reference.md
@@ -1152,7 +1152,7 @@ store('mySliderPlugin', {
## Server functions
-The Interactivity API comes with handy functions on the PHP part. Apart from [setting the store via server](#on-the-server-side), there is also a function to get and set Interactivity related config variables.
+The Interactivity API comes with handy functions that allow you to initialize and reference configuration options on the server. This is necessary to feed the initial data that the Server Directive Processing will use to modify the HTML markup before it's send to the browser. It is also a great way to leverage many of WordPress's APIs, like nonces, AJAX, and translations.
### wp_interactivity_config
@@ -1181,6 +1181,53 @@ This config can be retrieved on the client:
const { showLikeButton } = getConfig();
```
+### wp_interactivity_state
+
+`wp_interactivity_state` allows the initialization of the global state on the server, which will be used to process the directives on the server and then will be merged with any global state defined in the client.
+
+Initializing the global state on the server also allows you to use many critical WordPress APIs, including [AJAX](https://developer.wordpress.org/plugins/javascript/ajax/), or [nonces](https://developer.wordpress.org/plugins/javascript/enqueuing/#nonce).
+
+The `wp_interactivity_state` function receives two arguments, a string with the namespace that will be used as a reference and an associative array containing the values.
+
+Here is an example of passing the WP Admin AJAX endpoint with a nonce.
+
+```php
+// render.php
+
+wp_interactivity_state(
+ 'myPlugin',
+ array(
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'myPlugin_nonce' ),
+ ),
+);
+```
+
+```js
+// view.js
+
+const { state } = store( 'myPlugin', {
+ actions: {
+ *doSomething() {
+ try {
+ const formData = new FormData();
+ formData.append( 'action', 'do_something' );
+ formData.append( '_ajax_nonce', state.nonce );
+
+ const data = yield fetch( state.ajaxUrl, {
+ method: 'POST',
+ body: formData,
+ } ).then( ( response ) => response.json() );
+ console.log( 'Server data!', data );
+ } catch ( e ) {
+ // Something went wrong!
+ }
+ },
+ },
+ }
+);
+```
+
### wp_interactivity_process_directives
`wp_interactivity_process_directives` returns the updated HTML after the directives have been processed.
From e0b54b3596b35e7df15bd674f7c9ead918f05b07 Mon Sep 17 00:00:00 2001
From: Xinyu Liu
Date: Sun, 11 Aug 2024 18:35:46 +0800
Subject: [PATCH 032/126] Fix example of useBlockProps hook (#64363)
* Fix example of useBlockProps hook
* chore: tweaks
* chore: updates
Co-authored-by: meteorlxy
Co-authored-by: t-hamano
---
packages/block-editor/README.md | 23 ++++++++-----------
.../block-list/use-block-props/index.js | 4 ++--
2 files changed, 11 insertions(+), 16 deletions(-)
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index 776b217ba54f6e..c798015804b3e5 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -920,20 +920,15 @@ _Usage_
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit() {
-
- const blockProps = useBlockProps(
- className: 'my-custom-class',
- style: {
- color: '#222222',
- backgroundColor: '#eeeeee'
- }
- )
-
- return (
-
-
-
- )
+ const blockProps = useBlockProps( {
+ className: 'my-custom-class',
+ style: {
+ color: '#222222',
+ backgroundColor: '#eeeeee',
+ },
+ } );
+
+ return
;
}
```
diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js
index 6c44aa5c5d9705..15fb83139237cc 100644
--- a/packages/block-editor/src/components/block-list/use-block-props/index.js
+++ b/packages/block-editor/src/components/block-list/use-block-props/index.js
@@ -49,13 +49,13 @@ import { canBindBlock } from '../../../hooks/use-bindings-attributes';
*
* export default function Edit() {
*
- * const blockProps = useBlockProps(
+ * const blockProps = useBlockProps( {
* className: 'my-custom-class',
* style: {
* color: '#222222',
* backgroundColor: '#eeeeee'
* }
- * )
+ * } )
*
* return (
*
From 24f9ca62b0b83d3832233d276fea564b1714a172 Mon Sep 17 00:00:00 2001
From: Nick Diego
Date: Sun, 11 Aug 2024 23:41:56 -0500
Subject: [PATCH 033/126] Docs: Fix typos in the Block Filters documentation
(#64426)
Co-authored-by: ndiego
Co-authored-by: fabiankaegy
---
docs/reference-guides/filters/block-filters.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md
index e7a31c1e3bbc83..c70bb356a445a8 100644
--- a/docs/reference-guides/filters/block-filters.md
+++ b/docs/reference-guides/filters/block-filters.md
@@ -144,7 +144,7 @@ Filters the font-end content of any block. This filter has no impact on the beha
The callback function for this filter receives three parameters:
- `$block_content` (`string`): The block content.
-- `block` (`array`): The full block, including name and attributes.
+- `$block` (`array`): The full block, including name and attributes.
- `$instance` (`WP_Block`): The block instance.
In the following example, the class `example-class` is added to all Paragraph blocks on the front end. Here the [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) is used to easily add the class instead of relying on regex.
@@ -177,7 +177,7 @@ Filters the font-end content of the defined block. This is just a simpler form o
The callback function for this filter receives three parameters:
- `$block_content` (`string`): The block content.
-- `block` (`array`): The full block, including name and attributes.
+- `$block` (`array`): The full block, including name and attributes.
- `$instance` (`WP_Block`): The block instance.
In the following example, the class `example-class` is added to all Paragraph blocks on the front end. Notice that compared to the `render_block` example above, you no longer need to check the block type before modifying the content. Again, the [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) is used instead of regex.
From 92ea19f8cb946d051855aaa6791b0018a5d15fcb Mon Sep 17 00:00:00 2001
From: Luis Herranz
Date: Mon, 12 Aug 2024 10:18:00 +0200
Subject: [PATCH 034/126] Remove typed function from API reference (#64429)
Co-authored-by: luisherranz
Co-authored-by: fabiankaegy
Co-authored-by: atachibana
---
docs/reference-guides/interactivity-api/api-reference.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/reference-guides/interactivity-api/api-reference.md b/docs/reference-guides/interactivity-api/api-reference.md
index 29ec67cc17507f..46bd20bece0bda 100644
--- a/docs/reference-guides/interactivity-api/api-reference.md
+++ b/docs/reference-guides/interactivity-api/api-reference.md
@@ -776,7 +776,7 @@ Actions are just regular JavaScript functions. Usually triggered by the `data-wp
```ts
const { state, actions } = store("myPlugin", {
actions: {
- selectItem: (id?: number) => {
+ selectItem: ( id ) => {
const context = getContext();
// `id` is optional here, so this action can be used in a directive.
state.selected = id || context.id;
From 21cfde3f3ccba896a7026312ae646c74626d9621 Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Mon, 12 Aug 2024 12:37:18 +0400
Subject: [PATCH 035/126] Site Editor: Fix Template Parts post type preload
path (#64401)
* Site Editor: Fix Template Parts post type preload path
* Add backport changelog file
Co-authored-by: Mamaduka
Co-authored-by: tyxla
---
backport-changelog/6.7/7179.md | 3 +++
lib/compat/wordpress-6.7/rest-api.php | 31 +++++++++++++++++++++++++++
lib/load.php | 3 +++
3 files changed, 37 insertions(+)
create mode 100644 backport-changelog/6.7/7179.md
create mode 100644 lib/compat/wordpress-6.7/rest-api.php
diff --git a/backport-changelog/6.7/7179.md b/backport-changelog/6.7/7179.md
new file mode 100644
index 00000000000000..f359b6610a94e6
--- /dev/null
+++ b/backport-changelog/6.7/7179.md
@@ -0,0 +1,3 @@
+https://github.com/WordPress/wordpress-develop/pull/7179
+
+* https://github.com/WordPress/gutenberg/pull/64401
diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php
new file mode 100644
index 00000000000000..713d31c4632c74
--- /dev/null
+++ b/lib/compat/wordpress-6.7/rest-api.php
@@ -0,0 +1,31 @@
+name ) {
+ // Fixes post type name. It should be `type/wp_template_part`.
+ $parts_key = array_search( '/wp/v2/types/wp_template-part?context=edit', $paths, true );
+ if ( false !== $parts_key ) {
+ $paths[ $parts_key ] = '/wp/v2/types/wp_template_part?context=edit';
+ }
+ }
+
+ return $paths;
+}
+add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 );
diff --git a/lib/load.php b/lib/load.php
index 5a299f3b696968..4bb0ab88024a4a 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -40,6 +40,9 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php';
require __DIR__ . '/compat/wordpress-6.6/rest-api.php';
+ // WordPress 6.7 compat.
+ require __DIR__ . '/compat/wordpress-6.7/rest-api.php';
+
// Plugin specific code.
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';
require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php';
From acc9a38a331cd75b559e11e268ddde2eddfc2b89 Mon Sep 17 00:00:00 2001
From: James Koster
Date: Mon, 12 Aug 2024 09:52:36 +0100
Subject: [PATCH 036/126] Add elevation scale (#64108)
Co-authored-by: jameskoster
Co-authored-by: DaniGuardiola
Co-authored-by: ciampo
Co-authored-by: tyxla
Co-authored-by: jasmussen
Co-authored-by: richtabor
Co-authored-by: hanneslsm
---
packages/base-styles/_variables.scss | 26 +++++++++++++------
.../components/src/utils/config-values.js | 4 +++
2 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss
index 97eb513cf38aeb..0d3e139a7dd555 100644
--- a/packages/base-styles/_variables.scss
+++ b/packages/base-styles/_variables.scss
@@ -50,6 +50,22 @@ $radius-large: 8px; // Applied to containers with larger padding.
$radius-full: 9999px; // For lozenges.
$radius-round: 50%; // For circles and ovals.
+/**
+ * Elevation scale.
+ */
+
+// For sections and containers that group related content and controls, which may overlap other content. Example: Preview Frame.
+$elevation-x-small: 0 0.7px 1px rgba($black, 0.1), 0 1.2px 1.7px -0.2px rgba($black, 0.1), 0 2.3px 3.3px -0.5px rgba($black, 0.1);
+
+// For components that provide contextual feedback without being intrusive. Generally non-interruptive. Example: Tooltips, Snackbar.
+$elevation-small: 0 0.7px 1px 0 rgba(0, 0, 0, 0.12), 0 2.2px 3.7px -0.2px rgba(0, 0, 0, 0.12), 0 5.3px 7.3px -0.5px rgba(0, 0, 0, 0.12);
+
+// For components that offer additional actions. Example: Menus, Command Palette
+$elevation-medium: 0 0.7px 1px 0 rgba(0, 0, 0, 0.14), 0 4.2px 5.7px -0.2px rgba(0, 0, 0, 0.14), 0 7.3px 9.3px -0.5px rgba(0, 0, 0, 0.14);
+
+// For components that confirm decisions or handle necessary interruptions. Example: Modals.
+$elevation-large: 0 0.7px 1px rgba($black, 0.15), 0 2.7px 3.8px -0.2px rgba($black, 0.15), 0 5.5px 7.8px -0.3px rgba($black, 0.15), 0.1px 11.5px 16.4px -0.5px rgba($black, 0.15);
+
/**
* Dimensions.
*/
@@ -74,14 +90,6 @@ $modal-width-large: 840px;
$spinner-size: 16px;
$canvas-padding: $grid-unit-20;
-
-/**
- * Shadows.
- */
-
-$shadow-popover: 0 0.7px 1px rgba($black, 0.1), 0 1.2px 1.7px -0.2px rgba($black, 0.1), 0 2.3px 3.3px -0.5px rgba($black, 0.1);
-$shadow-modal: 0 0.7px 1px rgba($black, 0.15), 0 2.7px 3.8px -0.2px rgba($black, 0.15), 0 5.5px 7.8px -0.3px rgba($black, 0.15), 0.1px 11.5px 16.4px -0.5px rgba($black, 0.15);
-
/**
* Editor widths.
*/
@@ -107,6 +115,8 @@ $radio-input-size-sm: 24px; // Width & height for small viewports.
// Deprecated, please avoid using these.
$block-padding: 14px; // Used to define space between block footprint and surrouding borders.
$radius-block-ui: $radius-small;
+$shadow-popover: $elevation-x-small;
+$shadow-modal: $elevation-large;
/**
diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js
index ba92813bdbfb0f..65632170c9e3d7 100644
--- a/packages/components/src/utils/config-values.js
+++ b/packages/components/src/utils/config-values.js
@@ -74,6 +74,10 @@ export default Object.assign( {}, CONTROL_PROPS, TOGGLE_GROUP_CONTROL_PROPS, {
cardPaddingMedium: `${ space( 4 ) } ${ space( 6 ) }`,
cardPaddingLarge: `${ space( 6 ) } ${ space( 8 ) }`,
popoverShadow: `0 0.7px 1px rgba(0, 0, 0, 0.1), 0 1.2px 1.7px -0.2px rgba(0, 0, 0, 0.1), 0 2.3px 3.3px -0.5px rgba(0, 0, 0, 0.1)`,
+ elevationXSmall: `0 0.7px 1px rgba(0, 0, 0, 0.1), 0 1.2px 1.7px -0.2px rgba(0, 0, 0, 0.1), 0 2.3px 3.3px -0.5px rgba(0, 0, 0, 0.1)`,
+ elevationSmall: `0 0.7px 1px 0 rgba(0, 0, 0, 0.12), 0 2.2px 3.7px -0.2px rgba(0, 0, 0, 0.12), 0 5.3px 7.3px -0.5px rgba(0, 0, 0, 0.12)`,
+ elevationMedium: `0 0.7px 1px 0 rgba(0, 0, 0, 0.14), 0 4.2px 5.7px -0.2px rgba(0, 0, 0, 0.14), 0 7.3px 9.3px -0.5px rgba(0, 0, 0, 0.14)`,
+ elevationLarge: `0 0.7px 1px rgba(0, 0, 0, 0.15), 0 2.7px 3.8px -0.2px rgba(0, 0, 0, 0.15), 0 5.5px 7.8px -0.3px rgba(0, 0, 0, 0.15), 0.1px 11.5px 16.4px -0.5px rgba(0, 0, 0, 0.15)`,
surfaceBackgroundColor: COLORS.white,
surfaceBackgroundSubtleColor: '#F3F3F3',
surfaceBackgroundTintColor: '#F5F5F5',
From 4a8b166d623c442087a36eb41c35cc6dcd4e14ca Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 12 Aug 2024 11:47:55 +0200
Subject: [PATCH 037/126] DataViews: Refactor the edit function to be based on
discrete controls (#64404)
Co-authored-by: youknowriad
Co-authored-by: oandregal
Co-authored-by: ntsekouras
---
.../src/dataform-controls/datetime.tsx | 37 ++++++++++
.../dataform-controls/index.tsx | 25 +++++--
.../src/dataform-controls/integer.tsx | 38 ++++++++++
.../dataform-controls/radio.tsx | 9 ++-
.../src/dataform-controls/select.tsx | 52 ++++++++++++++
.../dataviews/src/dataform-controls/text.tsx | 40 +++++++++++
.../dataviews/src/field-types/datetime.tsx | 67 +----------------
.../dataviews/src/field-types/integer.tsx | 72 +------------------
packages/dataviews/src/field-types/text.tsx | 71 +-----------------
packages/dataviews/src/normalize-fields.ts | 2 +-
packages/dataviews/src/types.ts | 6 +-
11 files changed, 201 insertions(+), 218 deletions(-)
create mode 100644 packages/dataviews/src/dataform-controls/datetime.tsx
rename packages/dataviews/src/{components => }/dataform-controls/index.tsx (60%)
create mode 100644 packages/dataviews/src/dataform-controls/integer.tsx
rename packages/dataviews/src/{components => }/dataform-controls/radio.tsx (80%)
create mode 100644 packages/dataviews/src/dataform-controls/select.tsx
create mode 100644 packages/dataviews/src/dataform-controls/text.tsx
diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx
new file mode 100644
index 00000000000000..3ba22dc0c1b089
--- /dev/null
+++ b/packages/dataviews/src/dataform-controls/datetime.tsx
@@ -0,0 +1,37 @@
+/**
+ * WordPress dependencies
+ */
+import { BaseControl, TimePicker } from '@wordpress/components';
+import { useCallback } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { DataFormControlProps } from '../types';
+
+export default function DateTime< Item >( {
+ data,
+ field,
+ onChange,
+}: DataFormControlProps< Item > ) {
+ const { id, label } = field;
+ const value = field.getValue( { item: data } );
+
+ const onChangeControl = useCallback(
+ ( newValue: string | null ) => onChange( { [ id ]: newValue } ),
+ [ id, onChange ]
+ );
+
+ return (
+
+
+ { label }
+
+
+
+ );
+}
diff --git a/packages/dataviews/src/components/dataform-controls/index.tsx b/packages/dataviews/src/dataform-controls/index.tsx
similarity index 60%
rename from packages/dataviews/src/components/dataform-controls/index.tsx
rename to packages/dataviews/src/dataform-controls/index.tsx
index dd913269cd09ea..297e73c28f837c 100644
--- a/packages/dataviews/src/components/dataform-controls/index.tsx
+++ b/packages/dataviews/src/dataform-controls/index.tsx
@@ -10,15 +10,23 @@ import type {
DataFormControlProps,
Field,
FieldTypeDefinition,
-} from '../../types';
+} from '../types';
+import datetime from './datetime';
+import integer from './integer';
import radio from './radio';
+import select from './select';
+import text from './text';
interface FormControls {
[ key: string ]: ComponentType< DataFormControlProps< any > >;
}
const FORM_CONTROLS: FormControls = {
+ datetime,
+ integer,
radio,
+ select,
+ text,
};
export function getControl< Item >(
@@ -29,12 +37,19 @@ export function getControl< Item >(
return field.Edit;
}
- let control;
if ( typeof field.Edit === 'string' ) {
- control = getControlByType( field.Edit );
+ return getControlByType( field.Edit );
}
- return control || fieldTypeDefinition.Edit;
+ if ( field.elements ) {
+ return getControlByType( 'select' );
+ }
+
+ if ( typeof fieldTypeDefinition.Edit === 'string' ) {
+ return getControlByType( fieldTypeDefinition.Edit );
+ }
+
+ return fieldTypeDefinition.Edit;
}
export function getControlByType( type: string ) {
@@ -42,5 +57,5 @@ export function getControlByType( type: string ) {
return FORM_CONTROLS[ type ];
}
- return null;
+ throw 'Control ' + type + ' not found';
}
diff --git a/packages/dataviews/src/dataform-controls/integer.tsx b/packages/dataviews/src/dataform-controls/integer.tsx
new file mode 100644
index 00000000000000..f70a90ffe15239
--- /dev/null
+++ b/packages/dataviews/src/dataform-controls/integer.tsx
@@ -0,0 +1,38 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalNumberControl as NumberControl } from '@wordpress/components';
+import { useCallback } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { DataFormControlProps } from '../types';
+
+export default function Integer< Item >( {
+ data,
+ field,
+ onChange,
+ hideLabelFromVision,
+}: DataFormControlProps< Item > ) {
+ const { id, label, description } = field;
+ const value = field.getValue( { item: data } ) ?? '';
+ const onChangeControl = useCallback(
+ ( newValue: string | undefined ) =>
+ onChange( {
+ [ id ]: Number( newValue ),
+ } ),
+ [ id, onChange ]
+ );
+
+ return (
+
+ );
+}
diff --git a/packages/dataviews/src/components/dataform-controls/radio.tsx b/packages/dataviews/src/dataform-controls/radio.tsx
similarity index 80%
rename from packages/dataviews/src/components/dataform-controls/radio.tsx
rename to packages/dataviews/src/dataform-controls/radio.tsx
index d264aa6c24b7fb..3d616404e0c053 100644
--- a/packages/dataviews/src/components/dataform-controls/radio.tsx
+++ b/packages/dataviews/src/dataform-controls/radio.tsx
@@ -7,9 +7,9 @@ import { useCallback } from '@wordpress/element';
/**
* Internal dependencies
*/
-import type { DataFormControlProps } from '../../types';
+import type { DataFormControlProps } from '../types';
-export default function Edit< Item >( {
+export default function Radio< Item >( {
data,
field,
onChange,
@@ -20,10 +20,9 @@ export default function Edit< Item >( {
const onChangeControl = useCallback(
( newValue: string ) =>
- onChange( ( prevItem: Item ) => ( {
- ...prevItem,
+ onChange( {
[ id ]: newValue,
- } ) ),
+ } ),
[ id, onChange ]
);
diff --git a/packages/dataviews/src/dataform-controls/select.tsx b/packages/dataviews/src/dataform-controls/select.tsx
new file mode 100644
index 00000000000000..2b3bd9373fc155
--- /dev/null
+++ b/packages/dataviews/src/dataform-controls/select.tsx
@@ -0,0 +1,52 @@
+/**
+ * WordPress dependencies
+ */
+import { SelectControl } from '@wordpress/components';
+import { useCallback } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import type { DataFormControlProps } from '../types';
+
+export default function Select< Item >( {
+ data,
+ field,
+ onChange,
+ hideLabelFromVision,
+}: DataFormControlProps< Item > ) {
+ const { id, label } = field;
+ const value = field.getValue( { item: data } ) ?? '';
+ const onChangeControl = useCallback(
+ ( newValue: any ) =>
+ onChange( {
+ [ id ]: newValue,
+ } ),
+ [ id, onChange ]
+ );
+
+ const elements = [
+ /*
+ * Value can be undefined when:
+ *
+ * - the field is not required
+ * - in bulk editing
+ *
+ */
+ { label: __( 'Select item' ), value: '' },
+ ...( field?.elements ?? [] ),
+ ];
+
+ return (
+
+ );
+}
diff --git a/packages/dataviews/src/dataform-controls/text.tsx b/packages/dataviews/src/dataform-controls/text.tsx
new file mode 100644
index 00000000000000..7ac095f4abede7
--- /dev/null
+++ b/packages/dataviews/src/dataform-controls/text.tsx
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies
+ */
+import { TextControl } from '@wordpress/components';
+import { useCallback } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { DataFormControlProps } from '../types';
+
+export default function Text< Item >( {
+ data,
+ field,
+ onChange,
+ hideLabelFromVision,
+}: DataFormControlProps< Item > ) {
+ const { id, label, placeholder } = field;
+ const value = field.getValue( { item: data } );
+
+ const onChangeControl = useCallback(
+ ( newValue: string ) =>
+ onChange( {
+ [ id ]: newValue,
+ } ),
+ [ id, onChange ]
+ );
+
+ return (
+
+ );
+}
diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx
index c6b69048efe13c..aa97fc86c318c2 100644
--- a/packages/dataviews/src/field-types/datetime.tsx
+++ b/packages/dataviews/src/field-types/datetime.tsx
@@ -1,18 +1,7 @@
-/**
- * WordPress dependencies
- */
-import { BaseControl, TimePicker, SelectControl } from '@wordpress/components';
-import { useCallback } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-
/**
* Internal dependencies
*/
-import type {
- SortDirection,
- ValidationContext,
- DataFormControlProps,
-} from '../types';
+import type { SortDirection, ValidationContext } from '../types';
function sort( a: any, b: any, direction: SortDirection ) {
const timeA = new Date( a ).getTime();
@@ -32,60 +21,8 @@ function isValid( value: any, context?: ValidationContext ) {
return true;
}
-function Edit< Item >( {
- data,
- field,
- onChange,
-}: DataFormControlProps< Item > ) {
- const { id, label } = field;
- const value = field.getValue( { item: data } );
-
- const onChangeControl = useCallback(
- ( newValue: string | null ) => onChange( { [ id ]: newValue } ),
- [ id, onChange ]
- );
-
- if ( field.elements ) {
- const elements = [
- /*
- * Value can be undefined when:
- *
- * - the field is not required
- * - in bulk editing
- *
- */
- { label: __( 'Select item' ), value: '' },
- ...field.elements,
- ];
-
- return (
-
- );
- }
-
- return (
-
-
- { label }
-
-
-
- );
-}
-
export default {
sort,
isValid,
- Edit,
+ Edit: 'datetime',
};
diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx
index 38570ea6fec1a5..f57c8e382db816 100644
--- a/packages/dataviews/src/field-types/integer.tsx
+++ b/packages/dataviews/src/field-types/integer.tsx
@@ -1,21 +1,7 @@
-/**
- * WordPress dependencies
- */
-import {
- __experimentalNumberControl as NumberControl,
- SelectControl,
-} from '@wordpress/components';
-import { useCallback } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-
/**
* Internal dependencies
*/
-import type {
- SortDirection,
- ValidationContext,
- DataFormControlProps,
-} from '../types';
+import type { SortDirection, ValidationContext } from '../types';
function sort( a: any, b: any, direction: SortDirection ) {
return direction === 'asc' ? a - b : b - a;
@@ -41,62 +27,8 @@ function isValid( value: any, context?: ValidationContext ) {
return true;
}
-function Edit< Item >( {
- data,
- field,
- onChange,
- hideLabelFromVision,
-}: DataFormControlProps< Item > ) {
- const { id, label, description } = field;
- const value = field.getValue( { item: data } ) ?? '';
- const onChangeControl = useCallback(
- ( newValue: string | undefined ) =>
- onChange( {
- [ id ]: Number( newValue ),
- } ),
- [ id, onChange ]
- );
-
- if ( field.elements ) {
- const elements = [
- /*
- * Value can be undefined when:
- *
- * - the field is not required
- * - in bulk editing
- *
- */
- { label: __( 'Select item' ), value: '' },
- ...field.elements,
- ];
-
- return (
-
- );
- }
-
- return (
-
- );
-}
-
export default {
sort,
isValid,
- Edit,
+ Edit: 'integer',
};
diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx
index 5364017c629b00..76ff699d0848c4 100644
--- a/packages/dataviews/src/field-types/text.tsx
+++ b/packages/dataviews/src/field-types/text.tsx
@@ -1,18 +1,7 @@
-/**
- * WordPress dependencies
- */
-import { SelectControl, TextControl } from '@wordpress/components';
-import { useCallback } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-
/**
* Internal dependencies
*/
-import type {
- SortDirection,
- ValidationContext,
- DataFormControlProps,
-} from '../types';
+import type { SortDirection, ValidationContext } from '../types';
function sort( valueA: any, valueB: any, direction: SortDirection ) {
return direction === 'asc'
@@ -31,64 +20,8 @@ function isValid( value: any, context?: ValidationContext ) {
return true;
}
-function Edit< Item >( {
- data,
- field,
- onChange,
- hideLabelFromVision,
-}: DataFormControlProps< Item > ) {
- const { id, label, placeholder } = field;
- const value = field.getValue( { item: data } );
-
- const onChangeControl = useCallback(
- ( newValue: string ) =>
- onChange( {
- [ id ]: newValue,
- } ),
- [ id, onChange ]
- );
-
- if ( field.elements ) {
- const elements = [
- /*
- * Value can be undefined when:
- *
- * - the field is not required
- * - in bulk editing
- *
- */
- { label: __( 'Select item' ), value: '' },
- ...field.elements,
- ];
-
- return (
-
- );
- }
-
- return (
-
- );
-}
-
export default {
sort,
isValid,
- Edit,
+ Edit: 'text',
};
diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts
index 2cdde5b3343798..f9f95b5b8140dc 100644
--- a/packages/dataviews/src/normalize-fields.ts
+++ b/packages/dataviews/src/normalize-fields.ts
@@ -3,7 +3,7 @@
*/
import getFieldTypeDefinition from './field-types';
import type { Field, NormalizedField } from './types';
-import { getControl } from './components/dataform-controls';
+import { getControl } from './dataform-controls';
/**
* Apply default values and normalize the fields config.
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 34e74eabd7c7d8..7bbbc8cb863c09 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -63,9 +63,9 @@ export type FieldTypeDefinition< Item > = {
isValid: ( item: Item, context?: ValidationContext ) => boolean;
/**
- * Callback used to render an edit control for the field.
+ * Callback used to render an edit control for the field or control name.
*/
- Edit: ComponentType< DataFormControlProps< Item > >;
+ Edit: ComponentType< DataFormControlProps< Item > > | string;
};
/**
@@ -105,7 +105,7 @@ export type Field< Item > = {
/**
* Callback used to render an edit control for the field.
*/
- Edit?: ComponentType< DataFormControlProps< Item > > | 'radio';
+ Edit?: ComponentType< DataFormControlProps< Item > > | string;
/**
* Callback used to sort the field.
From 1e13913453eb520805e3aaadd6c2670be2c642e2 Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Mon, 12 Aug 2024 14:03:34 +0400
Subject: [PATCH 038/126] Edit Post: Avoid unnecessary post-template ID lookup
(#64431)
Co-authored-by: Mamaduka
Co-authored-by: ntsekouras
---
packages/edit-post/src/store/private-selectors.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/packages/edit-post/src/store/private-selectors.js b/packages/edit-post/src/store/private-selectors.js
index c151f935d68d50..0c0ac3c518024d 100644
--- a/packages/edit-post/src/store/private-selectors.js
+++ b/packages/edit-post/src/store/private-selectors.js
@@ -50,8 +50,11 @@ export const getEditedPostTemplateId = createRegistrySelector(
} else {
slugToCheck = postType === 'page' ? 'page' : `single-${ postType }`;
}
- return select( coreStore ).getDefaultTemplateId( {
- slug: slugToCheck,
- } );
+
+ if ( postType ) {
+ return select( coreStore ).getDefaultTemplateId( {
+ slug: slugToCheck,
+ } );
+ }
}
);
From da50610ac45e2cd18b59424d260bb3d693535061 Mon Sep 17 00:00:00 2001
From: JuanMa
Date: Mon, 12 Aug 2024 11:28:40 +0100
Subject: [PATCH 039/126] Docs/Dataviews component - Update README with missing
properties and recent changes (#64435)
Co-authored-by: juanmaguitar
Co-authored-by: oandregal
---
packages/dataviews/README.md | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index c82a748df98858..df537d2a8cecff 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -72,12 +72,12 @@ const STATUSES = [
const fields = [
{
id: 'title',
- header: 'Title',
+ label: 'Title',
enableHiding: false,
},
{
id: 'date',
- header: 'Date',
+ label: 'Date',
render: ( { item } ) => {
return (
{ getFormattedDate( item.date ) }
@@ -86,7 +86,7 @@ const fields = [
},
{
id: 'author',
- header: __( 'Author' ),
+ label: __( 'Author' ),
render: ( { item } ) => {
return (
{ item.author }
@@ -102,7 +102,7 @@ const fields = [
enableSorting: false
},
{
- header: __( 'Status' ),
+ label: __( 'Status' ),
id: 'status',
getValue: ( { item } ) =>
STATUSES.find( ( { value } ) => value === item.status )
@@ -119,13 +119,14 @@ const fields = [
Each field is an object with the following properties:
- `id`: identifier for the field. Unique.
-- `header`: the field's name to be shown in the UI.
+- `label`: the field's name to be shown in the UI.
- `getValue`: function that returns the value of the field, defaults to `field[id]`.
- `render`: function that renders the field. Optional, `getValue` will be used if `render` is not defined.
- `elements`: the set of valid values for the field's value.
- `type`: the type of the field. See "Field types".
- `enableSorting`: whether the data can be sorted by the given field. True by default.
- `enableHiding`: whether the field can be hidden. True by default.
+- `enableGlobalSearch`: whether the field is searchable. False by default.
- `filterBy`: configuration for the filters.
- `operators`: the list of operators supported by the field.
- `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter.
From d86a4999338183fc1f17067bed11d6b53ed46f87 Mon Sep 17 00:00:00 2001
From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com>
Date: Mon, 12 Aug 2024 16:27:41 +0530
Subject: [PATCH 040/126] Tag Cloud: Add border block support (#63579)
* Add border support to tag cloud block
* Prevent duplicate border styles in editor
* Add comments on style and edit component changes in tag cloud block
Co-authored-by: akasunil
Co-authored-by: aaronrobertshaw
---
packages/block-library/src/tag-cloud/block.json | 12 ++++++++++++
packages/block-library/src/tag-cloud/edit.js | 11 ++++++++++-
packages/block-library/src/tag-cloud/editor.scss | 4 +++-
3 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/packages/block-library/src/tag-cloud/block.json b/packages/block-library/src/tag-cloud/block.json
index 0c2095bff2a152..044bc0c5333768 100644
--- a/packages/block-library/src/tag-cloud/block.json
+++ b/packages/block-library/src/tag-cloud/block.json
@@ -51,6 +51,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-tag-cloud-editor"
diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js
index 9a2b531b30f8ab..8ced99271e4807 100644
--- a/packages/block-library/src/tag-cloud/edit.js
+++ b/packages/block-library/src/tag-cloud/edit.js
@@ -107,6 +107,15 @@ function TagCloudEdit( { attributes, setAttributes } ) {
setAttributes( updateObj );
};
+ // Remove border styles from the server-side attributes to prevent duplicate border.
+ const serverSideAttributes = {
+ ...attributes,
+ style: {
+ ...attributes?.style,
+ border: undefined,
+ },
+ };
+
const inspectorControls = (
@@ -188,7 +197,7 @@ function TagCloudEdit( { attributes, setAttributes } ) {
diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss
index de2a95a386fa85..d00a450174f2fd 100644
--- a/packages/block-library/src/tag-cloud/editor.scss
+++ b/packages/block-library/src/tag-cloud/editor.scss
@@ -1,4 +1,4 @@
-// The following styles are to prevent duplicate spacing for the tag cloud
+// The following styles are to prevent duplicate spacing and border 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
@@ -6,4 +6,6 @@
.wp-block-tag-cloud .wp-block-tag-cloud {
margin: 0;
padding: 0;
+ border: none;
+ border-radius: inherit;
}
From ba8c2c0d76957cb5534619ea496a79344fcacc6f Mon Sep 17 00:00:00 2001
From: Anton Vlasenko <43744263+anton-vlasenko@users.noreply.github.com>
Date: Mon, 12 Aug 2024 13:06:56 +0200
Subject: [PATCH 041/126] Fix gutenberg/gutenberg-coding-standards licensing
issues (#61913)
1. change the license to `GPL-2.0-or-later`;
2. remove third-party code from the `SinceTagSniff::find_docblock()` method;
3. remove third-party code from the `SinceTagSniff::is_function_call()` method;
4. remove the `SinceTagSniff::find_hook_docblock()` method and add the new `find_previous_line_token()` method (refactoring);
5. fix typo in the `SinceTagSniff::process()` method;
6. remove unused code from `bootstrap.php` and make variables use snake case;
7. add the `License` section to the `README.md` file.
Co-authored-by: anton-vlasenko
Co-authored-by: rodrigoprimo
Co-authored-by: azaozz
---
.../.phpcs.xml.dist | 9 +-
.../ForbiddenFunctionsAndClassesSniff.php | 3 +-
.../GuardedFunctionAndClassNamesSniff.php | 3 +-
.../Sniffs/Commenting/SinceTagSniff.php | 122 +++++++-----------
.../ValidBlockLibraryFunctionNameSniff.php | 3 +-
.../Gutenberg/Tests/AbstractSniffUnitTest.php | 10 +-
.../ForbiddenFunctionsAndClassesUnitTest.php | 3 +-
.../GuardedFunctionAndClassNamesUnitTest.php | 3 +-
.../Tests/Commenting/SinceTagUnitTest.php | 3 +-
.../ValidBlockLibraryFunctionNameUnitTest.php | 3 +-
test/php/gutenberg-coding-standards/README.md | 8 +-
.../Tests/bootstrap.php | 87 ++++---------
.../gutenberg-coding-standards/composer.json | 2 +-
13 files changed, 92 insertions(+), 167 deletions(-)
diff --git a/test/php/gutenberg-coding-standards/.phpcs.xml.dist b/test/php/gutenberg-coding-standards/.phpcs.xml.dist
index 7f717f65374d7b..7ebe60e3d28567 100644
--- a/test/php/gutenberg-coding-standards/.phpcs.xml.dist
+++ b/test/php/gutenberg-coding-standards/.phpcs.xml.dist
@@ -1,15 +1,8 @@
-
+
The Coding standard for the Gutenberg Coding Standards itself.
-
-
.
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/ForbiddenFunctionsAndClassesSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/ForbiddenFunctionsAndClassesSniff.php
index 87d966ecb56030..12d388cc378180 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/ForbiddenFunctionsAndClassesSniff.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/ForbiddenFunctionsAndClassesSniff.php
@@ -3,8 +3,7 @@
* Gutenberg Coding Standards.
*
* @package gutenberg/gutenberg-coding-standards
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Sniffs\CodeAnalysis;
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php
index c6e51119f1513a..d947d6d9382b35 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php
@@ -3,8 +3,7 @@
* Gutenberg Coding Standards.
*
* @package gutenberg/gutenberg-coding-standards
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Sniffs\CodeAnalysis;
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php
index f216f4f681f0e2..20f698954cb267 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/Commenting/SinceTagSniff.php
@@ -3,8 +3,7 @@
* Gutenberg Coding Standards.
*
* @package gutenberg/gutenberg-coding-standards
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Sniffs\Commenting;
@@ -22,7 +21,7 @@
/**
* This sniff verifies the presence of valid `@since` tags in the docblocks of various PHP structures
* and WordPress hooks. Supported structures include classes, interfaces, traits, enums, functions, methods and properties.
- * Files located within the __experimental block of the block-library are excluded from checks.
+ * Files located within the __experimental blocks of the block-library folder are excluded from checks.
*/
class SinceTagSniff implements Sniff {
@@ -147,7 +146,7 @@ protected function process_hook( File $phpcs_file, $stack_pointer ) {
$violation_codes = static::get_violation_codes( 'Hook' );
- $docblock = static::find_hook_docblock( $phpcs_file, $stack_pointer );
+ $docblock = static::find_docblock( $phpcs_file, $stack_pointer );
$version_tags = static::parse_since_tags( $phpcs_file, $docblock );
if ( empty( $version_tags ) ) {
@@ -436,114 +435,81 @@ protected function check_below_minimum_visibility( $visibility ) {
}
/**
- * Finds the docblock associated with a hook, starting from a specified position in the token stack.
- * Since a line containing a hook can include any type of tokens, this method backtracks through the tokens
- * to locate the first token on the current line. This token is then used as the starting point for searching the docblock.
+ * Finds the first token on the previous line relative to the stack pointer passed to the method.
*
* @param File $phpcs_file The file being scanned.
- * @param int $stack_pointer The position to start looking for the docblock.
- * @return array|false An associative array containing the start and end tokens of the docblock, or false if not found.
+ * @param int $stack_pointer The position to find the previous line token from.
+ * @return int|false The last token on the previous line, or false if not found.
*/
- protected static function find_hook_docblock( File $phpcs_file, $stack_pointer ) {
+ protected static function find_previous_line_token( File $phpcs_file, $stack_pointer ) {
$tokens = $phpcs_file->getTokens();
$current_line = $tokens[ $stack_pointer ]['line'];
- for ( $i = $stack_pointer; $i >= 0; $i-- ) {
- if ( $tokens[ $i ]['line'] < $current_line ) {
- // The previous token is on the previous line, so the current token is the first on the line.
- return static::find_docblock( $phpcs_file, $i + 1 );
+ for ( $token = $stack_pointer; $token >= 0; $token-- ) {
+ if ( $tokens[ $token ]['line'] < $current_line ) {
+ return $token;
}
}
- return static::find_docblock( $phpcs_file, 0 );
+ return false;
}
/**
- * Determines if a T_STRING token represents a function call.
- * The implementation was copied from PHPCompatibility\Sniffs\Extensions\RemovedExtensionsSniff::process().
+ * Finds the docblock preceding a specified position (stack pointer) in a given PHP file.
*
* @param File $phpcs_file The file being scanned.
- * @param int $stack_pointer The position of the T_STRING token in question.
- * @return bool True if the token represents a function call, false otherwise.
+ * @param int $stack_pointer The position (stack pointer) in the token stack from which to start searching backwards.
+ * @return array|false An associative array containing the start and end tokens of the docblock, or false if not found.
*/
- protected static function is_function_call( File $phpcs_file, $stack_pointer ) {
- $tokens = $phpcs_file->getTokens();
-
- // Find the next non-empty token.
- $open_bracket = $phpcs_file->findNext( Tokens::$emptyTokens, ( $stack_pointer + 1 ), null, true );
-
- if ( T_OPEN_PARENTHESIS !== $tokens[ $open_bracket ]['code'] ) {
- // Not a function call.
+ protected static function find_docblock( File $phpcs_file, $stack_pointer ) {
+ // It can be assumed that the DocBlock should end on the previous line, not the current one.
+ $previous_line_end_token = static::find_previous_line_token( $phpcs_file, $stack_pointer );
+ if ( false === $previous_line_end_token ) {
return false;
}
- if ( false === isset( $tokens[ $open_bracket ]['parenthesis_closer'] ) ) {
- // Not a function call.
+ $docblock_end_token = $phpcs_file->findPrevious( array( T_WHITESPACE ), $previous_line_end_token, null, true );
+
+ $tokens = $phpcs_file->getTokens();
+ if ( false === $docblock_end_token || T_DOC_COMMENT_CLOSE_TAG !== $tokens[ $docblock_end_token ]['code'] ) {
+ // Only "/**" style comments are supported.
return false;
}
- // Find the previous non-empty token.
- $search = Tokens::$emptyTokens;
- $search[] = T_BITWISE_AND;
- $previous = $phpcs_file->findPrevious( $search, ( $stack_pointer - 1 ), null, true );
-
- $previous_tokens_to_ignore = array(
- T_FUNCTION, // Function declaration.
- T_NEW, // Creating an object.
- T_OBJECT_OPERATOR, // Calling an object.
+ return array(
+ 'start_token' => $tokens[ $docblock_end_token ]['comment_opener'],
+ 'end_token' => $docblock_end_token,
);
-
- return ! in_array( $tokens[ $previous ]['code'], $previous_tokens_to_ignore, true );
}
/**
- * Finds the docblock preceding a specified position (stack pointer) in a given PHP file.
- * The implementation was copied from PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FunctionCommentSniff::process().
+ * Determines if a T_STRING token represents a function call.
*
* @param File $phpcs_file The file being scanned.
- * @param int $stack_pointer The position (stack pointer) in the token stack from which to start searching backwards.
- * @return array|false An associative array containing the start and end tokens of the docblock, or false if not found.
+ * @param int $stack_pointer The position of the T_STRING token in question.
+ * @return bool True if the token represents a function call, false otherwise.
*/
- protected static function find_docblock( File $phpcs_file, $stack_pointer ) {
- $tokens = $phpcs_file->getTokens();
- $ignore = Tokens::$methodPrefixes;
- $ignore[ T_WHITESPACE ] = T_WHITESPACE;
-
- for ( $comment_end = ( $stack_pointer - 1 ); $comment_end >= 0; $comment_end-- ) {
- if ( isset( $ignore[ $tokens[ $comment_end ]['code'] ] ) ) {
- continue;
- }
-
- if ( T_ATTRIBUTE_END === $tokens[ $comment_end ]['code']
- && isset( $tokens[ $comment_end ]['attribute_opener'] )
- ) {
- $comment_end = $tokens[ $comment_end ]['attribute_opener'];
- continue;
- }
+ protected static function is_function_call( File $phpcs_file, $stack_pointer ) {
+ $tokens = $phpcs_file->getTokens();
- break;
- }
+ // Find the previous non-empty token.
+ $previous = $phpcs_file->findPrevious( Tokens::$emptyTokens, ( $stack_pointer - 1 ), null, true );
- if ( $tokens[ $comment_end ]['code'] === T_COMMENT ) {
- // Inline comments might just be closing comments for
- // control structures or functions instead of function comments
- // using the wrong comment type. If there is other code on the line,
- // assume they relate to that code.
- $previous = $phpcs_file->findPrevious( $ignore, ( $comment_end - 1 ), null, true );
- if ( false !== $previous && $tokens[ $previous ]['line'] === $tokens[ $comment_end ]['line'] ) {
- $comment_end = $previous;
- }
- }
+ $previous_tokens_to_ignore = array(
+ T_NEW, // Creating an object.
+ T_OBJECT_OPERATOR, // Calling an object.
+ T_FUNCTION, // Function declaration.
+ );
- if ( T_DOC_COMMENT_CLOSE_TAG !== $tokens[ $comment_end ]['code'] ) {
- // Only "/**" style comments are supported.
+ if ( in_array( $tokens[ $previous ]['code'], $previous_tokens_to_ignore, true ) ) {
+ // This is an object or function declaration.
return false;
}
- return array(
- 'start_token' => $tokens[ $comment_end ]['comment_opener'],
- 'end_token' => $comment_end,
- );
+ // Find the next non-empty token.
+ $open_bracket = $phpcs_file->findNext( Tokens::$emptyTokens, ( $stack_pointer + 1 ), null, true );
+
+ return ( false !== $open_bracket ) && ( T_OPEN_PARENTHESIS === $tokens[ $open_bracket ]['code'] );
}
/**
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/NamingConventions/ValidBlockLibraryFunctionNameSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/NamingConventions/ValidBlockLibraryFunctionNameSniff.php
index 74608921c32d89..bf7dbc2fd85d3c 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/NamingConventions/ValidBlockLibraryFunctionNameSniff.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/NamingConventions/ValidBlockLibraryFunctionNameSniff.php
@@ -3,8 +3,7 @@
* Gutenberg Coding Standards.
*
* @package gutenberg/gutenberg-coding-standards
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Sniffs\NamingConventions;
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php
index 08838ce412fc3a..f1be5ab00b0750 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/AbstractSniffUnitTest.php
@@ -3,8 +3,7 @@
* An abstract class that all sniff unit tests must extend.
*
* @package gutenberg-coding-standards/gbc
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Tests;
@@ -14,6 +13,9 @@
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Sniffs\Sniff;
+/**
+ * An abstract test class that contains common methods for all sniff unit tests.
+ */
abstract class AbstractSniffUnitTest extends BaseAbstractSniffUnitTest {
/**
@@ -65,7 +67,7 @@ public function setCliValues( $filename, $config ) {
if ( ! isset( $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] )
|| ( ! $GLOBALS['PHP_CODESNIFFER_RULESETS']['Gutenberg'] instanceof Ruleset )
) {
- throw new \RuntimeException( $error_message );
+ throw new \RuntimeException( $error_message ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- this is non-production code.
}
// Backup the original Ruleset instance.
@@ -76,7 +78,7 @@ public function setCliValues( $filename, $config ) {
$sniff_fqcn = $this->get_sniff_fqcn();
if ( ! isset( $current_ruleset->sniffs[ $sniff_fqcn ] ) ) {
- throw new \RuntimeException( $error_message );
+ throw new \RuntimeException( $error_message ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- this is non-production code.
}
$sniff = $current_ruleset->sniffs[ $sniff_fqcn ];
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php
index 8026e88f1d9453..79719fba6039db 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/ForbiddenFunctionsAndClassesUnitTest.php
@@ -3,8 +3,7 @@
* Unit test class for Gutenberg Coding Standard.
*
* @package gutenberg-coding-standards/gbc
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Tests\CodeAnalysis;
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php
index 652f6b735378ce..b4e26fd2e69c4f 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.php
@@ -3,8 +3,7 @@
* Unit test class for Gutenberg Coding Standard.
*
* @package gutenberg-coding-standards/gbc
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Tests\CodeAnalysis;
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php
index bc7ca28c263ffe..1318687768c9dd 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/Commenting/SinceTagUnitTest.php
@@ -3,8 +3,7 @@
* Unit test class for Gutenberg Coding Standard.
*
* @package gutenberg-coding-standards/gbc
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Tests\Commenting;
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php b/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php
index 51174dd769d0a3..14a2cb1a97dd71 100644
--- a/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/NamingConventions/ValidBlockLibraryFunctionNameUnitTest.php
@@ -3,8 +3,7 @@
* Unit test class for Gutenberg Coding Standard.
*
* @package gutenberg-coding-standards/gbc
- * @link https://github.com/WordPress/gutenberg
- * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/WordPress/gutenberg/tree/trunk/test/php/gutenberg-coding-standards
*/
namespace GutenbergCS\Gutenberg\Tests\NamingConventions;
diff --git a/test/php/gutenberg-coding-standards/README.md b/test/php/gutenberg-coding-standards/README.md
index 51f6574fa20534..500abe2e5b4812 100644
--- a/test/php/gutenberg-coding-standards/README.md
+++ b/test/php/gutenberg-coding-standards/README.md
@@ -1,3 +1,9 @@
# Gutenberg Coding Standards for Gutenberg
-This project is a collection of [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) rules (sniffs) to validate code developed for Gutenberg. It ensures code quality and adherence to coding conventions.
\ No newline at end of file
+This project is a collection of [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) rules (sniffs) to validate code developed for Gutenberg. It ensures code quality and adherence to coding conventions.
+
+## License
+
+This project is licensed under the same terms as the Gutenberg project.
+
+Please refer to this [LICENSE.md](../../../LICENSE.md) file for detailed license information.
diff --git a/test/php/gutenberg-coding-standards/Tests/bootstrap.php b/test/php/gutenberg-coding-standards/Tests/bootstrap.php
index f28528bc1b9726..547a1ce303835f 100644
--- a/test/php/gutenberg-coding-standards/Tests/bootstrap.php
+++ b/test/php/gutenberg-coding-standards/Tests/bootstrap.php
@@ -1,86 +1,51 @@
true,
-);
-
-$allStandards = PHP_CodeSniffer\Util\Standards::getInstalledStandards();
-$allStandards[] = 'Generic';
+$available_standards = PHP_CodeSniffer\Util\Standards::getInstalledStandards();
+$ignored_standards = array( 'Generic' );
-$standardsToIgnore = array();
-foreach ( $allStandards as $standard ) {
- if ( isset( $gbcsStandards[ $standard ] ) === true ) {
+foreach ( $available_standards as $available_standard ) {
+ if ( 'Gutenberg' === $available_standard ) {
continue;
}
- $standardsToIgnore[] = $standard;
+ $ignored_standards[] = $available_standard;
}
-$standardsToIgnoreString = implode( ',', $standardsToIgnore );
+$ignore_standards_string = implode( ',', $ignored_standards );
-// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is not production, but test code.
-putenv( "PHPCS_IGNORE_TESTS={$standardsToIgnoreString}" );
+// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is non-production code.
+putenv( "PHPCS_IGNORE_TESTS={$ignore_standards_string}" );
-// Clean up.
-unset( $ds, $phpcsDir, $composerPHPCSPath, $allStandards, $standardsToIgnore, $standard, $standardsToIgnoreString );
+// Cleanup.
+unset( $dir_separator, $phpcs_path, $available_standards, $ignored_standards, $available_standard, $ignore_standards_string );
diff --git a/test/php/gutenberg-coding-standards/composer.json b/test/php/gutenberg-coding-standards/composer.json
index c1c27f81818aa5..2d79dc906a8d9e 100644
--- a/test/php/gutenberg-coding-standards/composer.json
+++ b/test/php/gutenberg-coding-standards/composer.json
@@ -8,7 +8,7 @@
"static analysis",
"Gutenberg"
],
- "license": "MIT",
+ "license": "GPL-2.0-or-later",
"authors": [
{
"name": "Contributors",
From 7969ba3b21006a33455c40df59f32e95058d26a0 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 12 Aug 2024 13:43:07 +0200
Subject: [PATCH 042/126] DataForm: Update the style of the datetime fields to
match the other types (#64438)
Co-authored-by: youknowriad
Co-authored-by: oandregal
---
.../dataviews/src/dataform-controls/datetime.tsx | 16 +++++++++++-----
.../dataviews/src/dataform-controls/style.scss | 4 ++++
packages/dataviews/src/style.scss | 1 +
3 files changed, 16 insertions(+), 5 deletions(-)
create mode 100644 packages/dataviews/src/dataform-controls/style.scss
diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx
index 3ba22dc0c1b089..b31c5c60bb9c6e 100644
--- a/packages/dataviews/src/dataform-controls/datetime.tsx
+++ b/packages/dataviews/src/dataform-controls/datetime.tsx
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { BaseControl, TimePicker } from '@wordpress/components';
+import { BaseControl, TimePicker, VisuallyHidden } from '@wordpress/components';
import { useCallback } from '@wordpress/element';
/**
@@ -13,6 +13,7 @@ export default function DateTime< Item >( {
data,
field,
onChange,
+ hideLabelFromVision,
}: DataFormControlProps< Item > ) {
const { id, label } = field;
const value = field.getValue( { item: data } );
@@ -23,10 +24,15 @@ export default function DateTime< Item >( {
);
return (
-
-
- { label }
-
+
+ { ! hideLabelFromVision && (
+
+ { label }
+
+ ) }
+ { hideLabelFromVision && (
+ { label }
+ ) }
Date: Mon, 12 Aug 2024 14:48:18 +0200
Subject: [PATCH 043/126] Script Modules: Move data passing to 6.7 compat file
(#64006)
Move Script Modules data passing to compatibility directory.
Add conditions to ensure the functionality is duplicated when present in Core.
This prevents duplicate script tags with duplicate IDs from being printed.
This feature is planned to ship in WordPress 6.7: https://core.trac.wordpress.org/ticket/61510
---
lib/compat/wordpress-6.7/script-modules.php | 104 ++++++++++++++++++++
lib/experimental/script-modules.php | 93 -----------------
lib/load.php | 1 +
3 files changed, 105 insertions(+), 93 deletions(-)
create mode 100644 lib/compat/wordpress-6.7/script-modules.php
diff --git a/lib/compat/wordpress-6.7/script-modules.php b/lib/compat/wordpress-6.7/script-modules.php
new file mode 100644
index 00000000000000..0a440ec81688d2
--- /dev/null
+++ b/lib/compat/wordpress-6.7/script-modules.php
@@ -0,0 +1,104 @@
+setAccessible( true );
+ $get_import_map = new ReflectionMethod( 'WP_Script_Modules', 'get_import_map' );
+ $get_import_map->setAccessible( true );
+
+ $modules = array();
+ foreach ( array_keys( $get_marked_for_enqueue->invoke( wp_script_modules() ) ) as $id ) {
+ $modules[ $id ] = true;
+ }
+ foreach ( array_keys( $get_import_map->invoke( wp_script_modules() )['imports'] ) as $id ) {
+ $modules[ $id ] = true;
+ }
+
+ foreach ( array_keys( $modules ) as $module_id ) {
+ /**
+ * Filters data associated with a given Script Module.
+ *
+ * Script Modules may require data that is required for initialization or is essential to
+ * have immediately available on page load. These are suitable use cases for this data.
+ *
+ * This is best suited to a minimal set of data and is not intended to replace the REST API.
+ *
+ * If the filter returns no data (an empty array), nothing will be embedded in the page.
+ *
+ * The data for a given Script Module, if provided, will be JSON serialized in a script tag
+ * with an ID like `wp-script-module-data-{$module_id}`.
+ *
+ * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID that
+ * the data is associated with.
+ *
+ * @param array $data The data that should be associated with the array.
+ */
+ $data = apply_filters( "script_module_data_{$module_id}", array() );
+
+ if ( is_array( $data ) && ! empty( $data ) ) {
+ /*
+ * This data will be printed as JSON inside a script tag like this:
+ *
+ *
+ * A script tag must be closed by a sequence beginning with ``. It's impossible to
+ * close a script tag without using `<`. We ensure that `<` is escaped and `/` can
+ * remain unescaped, so `` will be printed as `\u003C/script\u00E3`.
+ *
+ * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
+ * - JSON_UNESCAPED_SLASHES: Don't escape /.
+ *
+ * If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
+ *
+ * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
+ * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
+ * JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was
+ * before PHP 7.1 without this constant. Available as of PHP 7.1.0.
+ *
+ * The JSON specification requires encoding in UTF-8, so if the generated HTML page
+ * is not encoded in UTF-8 then it's not safe to include those literals. They must
+ * be escaped to avoid encoding issues.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements.
+ * @see https://www.php.net/manual/en/json.constants.php for details on these constants.
+ * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing.
+ */
+ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
+ if ( 'UTF-8' !== get_option( 'blog_charset' ) ) {
+ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
+ }
+
+ wp_print_inline_script_tag(
+ wp_json_encode( $data, $json_encode_flags ),
+ array(
+ 'type' => 'application/json',
+ 'id' => "wp-script-module-data-{$module_id}",
+ )
+ );
+ }
+ }
+}
+
+add_action(
+ 'after_setup_theme',
+ function () {
+ if ( ! has_action( 'wp_footer', array( wp_script_modules(), 'print_script_module_data' ) ) ) {
+ add_action( 'wp_footer', 'gutenberg_print_script_module_data' );
+ }
+
+ if ( ! has_action( 'admin_print_footer_scripts', array( wp_script_modules(), 'print_script_module_data' ) ) ) {
+ add_action( 'admin_print_footer_scripts', 'gutenberg_print_script_module_data' );
+ }
+ },
+ 20
+);
diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php
index 0093c2e974568f..5a14e1418ed6de 100644
--- a/lib/experimental/script-modules.php
+++ b/lib/experimental/script-modules.php
@@ -200,96 +200,3 @@ function gutenberg_dequeue_module( $module_identifier ) {
_deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_dequeue_script_module' );
wp_script_modules()->dequeue( $module_identifier );
}
-
-
-/**
- * Print data associated with Script Modules in Script tags.
- *
- * This embeds data in the page HTML so that it is available on page load.
- *
- * Data can be associated with a given Script Module by using the
- * `script_module_data_{$module_id}` filter.
- *
- * The data for a given Script Module will be JSON serialized in a script tag with an ID
- * like `wp-script-module-data-{$module_id}`.
- */
-function gutenberg_print_script_module_data(): void {
- $get_marked_for_enqueue = new ReflectionMethod( 'WP_Script_Modules', 'get_marked_for_enqueue' );
- $get_marked_for_enqueue->setAccessible( true );
- $get_import_map = new ReflectionMethod( 'WP_Script_Modules', 'get_import_map' );
- $get_import_map->setAccessible( true );
-
- $modules = array();
- foreach ( array_keys( $get_marked_for_enqueue->invoke( wp_script_modules() ) ) as $id ) {
- $modules[ $id ] = true;
- }
- foreach ( array_keys( $get_import_map->invoke( wp_script_modules() )['imports'] ) as $id ) {
- $modules[ $id ] = true;
- }
-
- foreach ( array_keys( $modules ) as $module_id ) {
- /**
- * Filters data associated with a given Script Module.
- *
- * Script Modules may require data that is required for initialization or is essential to
- * have immediately available on page load. These are suitable use cases for this data.
- *
- * This is best suited to a minimal set of data and is not intended to replace the REST API.
- *
- * If the filter returns no data (an empty array), nothing will be embedded in the page.
- *
- * The data for a given Script Module, if provided, will be JSON serialized in a script tag
- * with an ID like `wp-script-module-data-{$module_id}`.
- *
- * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID that
- * the data is associated with.
- *
- * @param array $data The data that should be associated with the array.
- */
- $data = apply_filters( "script_module_data_{$module_id}", array() );
-
- if ( is_array( $data ) && ! empty( $data ) ) {
- /*
- * This data will be printed as JSON inside a script tag like this:
- *
- *
- * A script tag must be closed by a sequence beginning with ``. It's impossible to
- * close a script tag without using `<`. We ensure that `<` is escaped and `/` can
- * remain unescaped, so `` will be printed as `\u003C/script\u00E3`.
- *
- * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
- * - JSON_UNESCAPED_SLASHES: Don't escape /.
- *
- * If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
- *
- * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
- * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
- * JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was
- * before PHP 7.1 without this constant. Available as of PHP 7.1.0.
- *
- * The JSON specification requires encoding in UTF-8, so if the generated HTML page
- * is not encoded in UTF-8 then it's not safe to include those literals. They must
- * be escaped to avoid encoding issues.
- *
- * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements.
- * @see https://www.php.net/manual/en/json.constants.php for details on these constants.
- * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing.
- */
- $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
- if ( 'UTF-8' !== get_option( 'blog_charset' ) ) {
- $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
- }
-
- wp_print_inline_script_tag(
- wp_json_encode( $data, $json_encode_flags ),
- array(
- 'type' => 'application/json',
- 'id' => "wp-script-module-data-{$module_id}",
- )
- );
- }
- }
-}
-
-add_action( 'wp_footer', 'gutenberg_print_script_module_data' );
-add_action( 'admin_print_footer_scripts', 'gutenberg_print_script_module_data' );
diff --git a/lib/load.php b/lib/load.php
index 4bb0ab88024a4a..c5f12af1654df2 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -103,6 +103,7 @@ function gutenberg_is_experiment_enabled( $name ) {
// WordPress 6.7 compat.
require __DIR__ . '/compat/wordpress-6.7/blocks.php';
require __DIR__ . '/compat/wordpress-6.7/block-bindings.php';
+require __DIR__ . '/compat/wordpress-6.7/script-modules.php';
// Experimental features.
require __DIR__ . '/experimental/block-editor-settings-mobile.php';
From 9f5ffd2e8dd5ffc0abedf2b258dd5ace6b3bdb1e Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 12 Aug 2024 14:58:37 +0200
Subject: [PATCH 044/126] DataViews Extensibility: Allow unregistering the
duplicate template part action (#64388)
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
---
.../src/components/post-actions/actions.js | 72 +------------------
.../actions/duplicate-template-part.tsx | 70 ++++++++++++++++++
.../src/dataviews/store/private-actions.ts | 8 +++
packages/editor/src/dataviews/types.ts | 4 +-
4 files changed, 83 insertions(+), 71 deletions(-)
create mode 100644 packages/editor/src/dataviews/actions/duplicate-template-part.tsx
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index ff5cd7ddbb5452..0df73d1996deb5 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -9,7 +9,6 @@ import { store as coreStore } from '@wordpress/core-data';
import { __, sprintf, _x } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { useMemo, useState, useEffect } from '@wordpress/element';
-import { parse } from '@wordpress/blocks';
import { DataForm } from '@wordpress/dataviews';
import {
Button,
@@ -27,7 +26,6 @@ import {
} from '../../store/constants';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
-import { CreateTemplatePartModalContents } from '../create-template-part-modal';
import { getItemTitle } from '../../dataviews/actions/utils';
// TODO: this should be shared with other components (see post-fields in edit-site).
@@ -268,75 +266,14 @@ const useDuplicatePostAction = ( postType ) => {
);
};
-export const duplicateTemplatePartAction = {
- id: 'duplicate-template-part',
- label: _x( 'Duplicate', 'action label' ),
- isEligible: ( item ) => item.type === TEMPLATE_PART_POST_TYPE,
- modalHeader: _x( 'Duplicate template part', 'action label' ),
- RenderModal: ( { items, closeModal } ) => {
- const [ item ] = items;
- const blocks = useMemo( () => {
- return (
- item.blocks ??
- parse(
- typeof item.content === 'string'
- ? item.content
- : item.content.raw,
- {
- __unstableSkipMigrationLogs: true,
- }
- )
- );
- }, [ item.content, item.blocks ] );
- const { createSuccessNotice } = useDispatch( noticesStore );
- function onTemplatePartSuccess() {
- createSuccessNotice(
- sprintf(
- // translators: %s: The new template part's title e.g. 'Call to action (copy)'.
- __( '"%s" duplicated.' ),
- getItemTitle( item )
- ),
- { type: 'snackbar', id: 'edit-site-patterns-success' }
- );
- closeModal();
- }
- return (
-
- );
- },
-};
-
export function usePostActions( { postType, onActionPerformed, context } ) {
- const {
- defaultActions,
- postTypeObject,
- userCanCreatePostType,
- isBlockBasedTheme,
- } = useSelect(
+ const { defaultActions, postTypeObject } = useSelect(
( select ) => {
- const { getPostType, canUser, getCurrentTheme } =
- select( coreStore );
+ const { getPostType } = select( coreStore );
const { getEntityActions } = unlock( select( editorStore ) );
return {
postTypeObject: getPostType( postType ),
defaultActions: getEntityActions( 'postType', postType ),
- userCanCreatePostType: canUser( 'create', {
- kind: 'postType',
- name: postType,
- } ),
- isBlockBasedTheme: getCurrentTheme()?.is_block_theme,
};
},
[ postType ]
@@ -368,10 +305,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
! isPattern &&
duplicatePostAction
: false,
- isTemplateOrTemplatePart &&
- userCanCreatePostType &&
- isBlockBasedTheme &&
- duplicateTemplatePartAction,
...defaultActions,
].filter( Boolean );
// Filter actions based on provided context. If not provided
@@ -437,7 +370,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
return actions;
}, [
defaultActions,
- userCanCreatePostType,
isTemplateOrTemplatePart,
isPattern,
postTypeObject?.viewable,
diff --git a/packages/editor/src/dataviews/actions/duplicate-template-part.tsx b/packages/editor/src/dataviews/actions/duplicate-template-part.tsx
new file mode 100644
index 00000000000000..fa3cf39ba76268
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/duplicate-template-part.tsx
@@ -0,0 +1,70 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { __, sprintf, _x } from '@wordpress/i18n';
+import { store as noticesStore } from '@wordpress/notices';
+import { useMemo } from '@wordpress/element';
+// @ts-ignore
+import { parse } from '@wordpress/blocks';
+import type { Action } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+import { TEMPLATE_PART_POST_TYPE } from '../../store/constants';
+import { CreateTemplatePartModalContents } from '../../components/create-template-part-modal';
+import { getItemTitle } from './utils';
+import type { TemplatePart } from '../types';
+
+const duplicateTemplatePart: Action< TemplatePart > = {
+ id: 'duplicate-template-part',
+ label: _x( 'Duplicate', 'action label' ),
+ isEligible: ( item ) => item.type === TEMPLATE_PART_POST_TYPE,
+ modalHeader: _x( 'Duplicate template part', 'action label' ),
+ RenderModal: ( { items, closeModal } ) => {
+ const [ item ] = items;
+ const blocks = useMemo( () => {
+ return (
+ item.blocks ??
+ parse(
+ typeof item.content === 'string'
+ ? item.content
+ : item.content.raw,
+ {
+ __unstableSkipMigrationLogs: true,
+ }
+ )
+ );
+ }, [ item.content, item.blocks ] );
+ const { createSuccessNotice } = useDispatch( noticesStore );
+ function onTemplatePartSuccess() {
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: The new template part's title e.g. 'Call to action (copy)'.
+ __( '"%s" duplicated.' ),
+ getItemTitle( item )
+ ),
+ { type: 'snackbar', id: 'edit-site-patterns-success' }
+ );
+ closeModal?.();
+ }
+ return (
+
+ );
+ },
+};
+
+export default duplicateTemplatePart;
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index 6854c29bb0c4e4..80449d1b7a0798 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -10,6 +10,7 @@ import { doAction } from '@wordpress/hooks';
*/
import deletePost from '../actions/delete-post';
import duplicatePattern from '../actions/duplicate-pattern';
+import duplicateTemplatePart from '../actions/duplicate-template-part';
import exportPattern from '../actions/export-pattern';
import resetPost from '../actions/reset-post';
import trashPost from '../actions/trash-post';
@@ -81,8 +82,15 @@ export const registerPostTypeActions =
kind: 'postType',
name: postType,
} );
+ const currentTheme = await registry
+ .resolveSelect( coreStore )
+ .getCurrentTheme();
const actions = [
+ postTypeConfig.slug === 'wp_template_part' &&
+ canCreate &&
+ currentTheme?.is_block_theme &&
+ duplicateTemplatePart,
canCreate && postTypeConfig.slug === 'wp_block'
? duplicatePattern
: undefined,
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
index 80b6f3c5ceb852..5750ab96eeae81 100644
--- a/packages/editor/src/dataviews/types.ts
+++ b/packages/editor/src/dataviews/types.ts
@@ -10,8 +10,10 @@ type PostStatus =
export interface BasePost {
status?: PostStatus;
title: string | { rendered: string } | { raw: string };
+ content: string | { raw: string; rendered: string };
type: string;
id: string | number;
+ blocks?: Object[];
}
export interface Template extends BasePost {
@@ -27,12 +29,12 @@ export interface TemplatePart extends BasePost {
source: string;
has_theme_file: boolean;
id: string;
+ area: string;
}
export interface Pattern extends BasePost {
slug: string;
title: { raw: string };
- content: { raw: string } | string;
wp_pattern_sync_status: string;
}
From c73aa361c4bd82d8b3ee004e5195a22158cad1bf Mon Sep 17 00:00:00 2001
From: Ben Dwyer
Date: Mon, 12 Aug 2024 14:05:40 +0100
Subject: [PATCH 045/126] Block Insertion: Clear the insertion point when
selecting a different block or clearing block selection (#64048)
* Clear the insertion point when selecting a different block or clearing block selection
* Use the block insertion point in the inserter, not the the one from the editor package
* use block insertion point index
* Don't change the insertion point when clearing block selection
* Also set the insertion point in the quick inserter
Co-authored-by: scruffian
Co-authored-by: MaggieCabrera
---
.../src/components/inserter/quick-inserter.js | 5 ++++-
packages/block-editor/src/store/reducer.js | 2 ++
.../src/components/inserter-sidebar/index.js | 14 ++++++++++----
3 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js
index aa3d54e87a7fd1..f4c52c04d20c4b 100644
--- a/packages/block-editor/src/components/inserter/quick-inserter.js
+++ b/packages/block-editor/src/components/inserter/quick-inserter.js
@@ -9,7 +9,7 @@ import clsx from 'clsx';
import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Button, SearchControl } from '@wordpress/components';
-import { useSelect } from '@wordpress/data';
+import { useDispatch, useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -82,6 +82,8 @@ export default function QuickInserter( {
}
}, [ setInserterIsOpened ] );
+ const { showInsertionPoint } = useDispatch( blockEditorStore );
+
// When clicking Browse All select the appropriate block so as
// the insertion point can work as expected.
const onBrowseAll = () => {
@@ -91,6 +93,7 @@ export default function QuickInserter( {
filterValue,
onSelect,
} );
+ showInsertionPoint( rootClientId, insertionIndex );
};
let maxBlockPatterns = 0;
diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index cd4569c45e5801..91d853a50a6a42 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -1624,6 +1624,8 @@ export function insertionPoint( state = null, action ) {
}
case 'HIDE_INSERTION_POINT':
+ case 'CLEAR_SELECTED_BLOCK':
+ case 'SELECT_BLOCK':
return null;
}
diff --git a/packages/editor/src/components/inserter-sidebar/index.js b/packages/editor/src/components/inserter-sidebar/index.js
index 675ae5e11544bc..bf613b5c8c001a 100644
--- a/packages/editor/src/components/inserter-sidebar/index.js
+++ b/packages/editor/src/components/inserter-sidebar/index.js
@@ -22,6 +22,7 @@ const { PrivateInserterLibrary } = unlock( blockEditorPrivateApis );
export default function InserterSidebar() {
const {
+ blockInsertionPoint,
blockSectionRootClientId,
inserterSidebarToggleRef,
insertionPoint,
@@ -33,8 +34,12 @@ export default function InserterSidebar() {
getInsertionPoint,
isPublishSidebarOpened,
} = unlock( select( editorStore ) );
- const { getBlockRootClientId, __unstableGetEditorMode, getSettings } =
- select( blockEditorStore );
+ const {
+ getBlockInsertionPoint,
+ getBlockRootClientId,
+ __unstableGetEditorMode,
+ getSettings,
+ } = select( blockEditorStore );
const { get } = select( preferencesStore );
const { getActiveComplementaryArea } = select( interfaceStore );
const getBlockSectionRootClientId = () => {
@@ -47,6 +52,7 @@ export default function InserterSidebar() {
return getBlockRootClientId();
};
return {
+ blockInsertionPoint: getBlockInsertionPoint(),
inserterSidebarToggleRef: getInserterSidebarToggleRef(),
insertionPoint: getInsertionPoint(),
showMostUsedBlocks: get( 'core', 'mostUsedBlocks' ),
@@ -85,9 +91,9 @@ export default function InserterSidebar() {
showInserterHelpPanel
shouldFocusBlock={ isMobileViewport }
rootClientId={
- blockSectionRootClientId ?? insertionPoint.rootClientId
+ blockSectionRootClientId ?? blockInsertionPoint.rootClientId
}
- __experimentalInsertionIndex={ insertionPoint.insertionIndex }
+ __experimentalInsertionIndex={ blockInsertionPoint.index }
onSelect={ insertionPoint.onSelect }
__experimentalInitialTab={ insertionPoint.tab }
__experimentalInitialCategory={ insertionPoint.category }
From d6a27a056c5fe5c995a96f05a10105b80c7fa838 Mon Sep 17 00:00:00 2001
From: JuanMa
Date: Mon, 12 Aug 2024 15:47:54 +0100
Subject: [PATCH 046/126] DataViews: document missing action properties and
link to storybook example (#64442)
Co-authored-by: juanmaguitar
Co-authored-by: oandregal
---
packages/dataviews/README.md | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index df537d2a8cecff..56cecedaef66ca 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -30,6 +30,9 @@ const Example = () => {
};
```
+> [!TIP]
+> At https://wordpress.github.io/gutenberg/?path=/docs/dataviews-dataviews--docs there's an example implementation of the Dataviews component."
+
## Properties
### `data`: `Object[]`
@@ -79,41 +82,37 @@ const fields = [
id: 'date',
label: 'Date',
render: ( { item } ) => {
- return (
- { getFormattedDate( item.date ) }
- );
- }
+ return { getFormattedDate( item.date ) } ;
+ },
},
{
id: 'author',
label: __( 'Author' ),
render: ( { item } ) => {
- return (
- { item.author }
- );
+ return { item.author } ;
},
elements: [
{ value: 1, label: 'Admin' },
- { value: 2, label: 'User' }
+ { value: 2, label: 'User' },
],
filterBy: {
- operators: [ 'is', 'isNot' ]
+ operators: [ 'is', 'isNot' ],
},
- enableSorting: false
+ enableSorting: false,
},
{
label: __( 'Status' ),
id: 'status',
getValue: ( { item } ) =>
- STATUSES.find( ( { value } ) => value === item.status )
- ?.label ?? item.status,
+ STATUSES.find( ( { value } ) => value === item.status )?.label ??
+ item.status,
elements: STATUSES,
filterBy: {
operators: [ 'isAny' ],
},
enableSorting: false,
},
-]
+];
```
Each field is an object with the following properties:
@@ -256,6 +255,8 @@ Each action is an object with the following properties:
- `callback`: function, required unless `RenderModal` is provided. Callback function that takes the record as input and performs the required action.
- `RenderModal`: ReactElement, optional. If an action requires that some UI be rendered in a modal, it can provide a component which takes as props the record as `item` and a `closeModal` function. When this prop is provided, the `callback` property is ignored.
- `hideModalHeader`: boolean, optional. This property is used in combination with `RenderModal` and controls the visibility of the modal's header. If the action renders a modal and doesn't hide the header, the action's label is going to be used in the modal's header.
+- `supportsBulk`: Whether the action can be used as a bulk action. False by default.
+- `disabled`: Whether the action is disabled. False by default.
### `paginationInfo`: `Object`
@@ -289,8 +290,8 @@ const defaultLayouts = {
table: {
layout: {
primaryKey: 'my-key',
- }
- }
+ },
+ },
};
```
From 52171c992fbb9a43e69a25054e44de595ac7390d Mon Sep 17 00:00:00 2001
From: James Koster
Date: Mon, 12 Aug 2024 16:03:13 +0100
Subject: [PATCH 047/126] Update DropdownMenuV2 elevation, remove unused config
value (#64432)
Co-authored-by: jameskoster
Co-authored-by: DaniGuardiola
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/dropdown-menu-v2/styles.ts | 2 +-
packages/components/src/utils/config-values.js | 1 -
3 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 96bf76815e71dd..57ba69197cfa98 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -11,6 +11,7 @@
- `Composite`: improve Storybook examples and add interactive controls ([#64397](https://github.com/WordPress/gutenberg/pull/64397)).
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
+- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
## 28.5.0 (2024-08-07)
diff --git a/packages/components/src/dropdown-menu-v2/styles.ts b/packages/components/src/dropdown-menu-v2/styles.ts
index ab7c763b5f4569..950a549f8566a4 100644
--- a/packages/components/src/dropdown-menu-v2/styles.ts
+++ b/packages/components/src/dropdown-menu-v2/styles.ts
@@ -31,7 +31,7 @@ const ITEM_PADDING_INLINE = space( 3 );
const DEFAULT_BORDER_COLOR = COLORS.gray[ 300 ];
const DIVIDER_COLOR = COLORS.gray[ 200 ];
const TOOLBAR_VARIANT_BORDER_COLOR = COLORS.gray[ '900' ];
-const DEFAULT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ DEFAULT_BORDER_COLOR }, ${ CONFIG.popoverShadow }`;
+const DEFAULT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ DEFAULT_BORDER_COLOR }, ${ CONFIG.elevationXSmall }`;
const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VARIANT_BORDER_COLOR }`;
const GRID_TEMPLATE_COLS = 'minmax( 0, max-content ) 1fr';
diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js
index 65632170c9e3d7..02c6f69544c2bf 100644
--- a/packages/components/src/utils/config-values.js
+++ b/packages/components/src/utils/config-values.js
@@ -73,7 +73,6 @@ export default Object.assign( {}, CONTROL_PROPS, TOGGLE_GROUP_CONTROL_PROPS, {
cardPaddingSmall: `${ space( 4 ) }`,
cardPaddingMedium: `${ space( 4 ) } ${ space( 6 ) }`,
cardPaddingLarge: `${ space( 6 ) } ${ space( 8 ) }`,
- popoverShadow: `0 0.7px 1px rgba(0, 0, 0, 0.1), 0 1.2px 1.7px -0.2px rgba(0, 0, 0, 0.1), 0 2.3px 3.3px -0.5px rgba(0, 0, 0, 0.1)`,
elevationXSmall: `0 0.7px 1px rgba(0, 0, 0, 0.1), 0 1.2px 1.7px -0.2px rgba(0, 0, 0, 0.1), 0 2.3px 3.3px -0.5px rgba(0, 0, 0, 0.1)`,
elevationSmall: `0 0.7px 1px 0 rgba(0, 0, 0, 0.12), 0 2.2px 3.7px -0.2px rgba(0, 0, 0, 0.12), 0 5.3px 7.3px -0.5px rgba(0, 0, 0, 0.12)`,
elevationMedium: `0 0.7px 1px 0 rgba(0, 0, 0, 0.14), 0 4.2px 5.7px -0.2px rgba(0, 0, 0, 0.14), 0 7.3px 9.3px -0.5px rgba(0, 0, 0, 0.14)`,
From 9c96970fdfa010b2b0b710c79c13684398b792f1 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Tue, 13 Aug 2024 01:22:31 +0900
Subject: [PATCH 048/126] Add margin-bottom lint rules for BaseControl (#64355)
* Fix in Gallery block
* Fix in Latest Posts block
* Fix in Search block
* Fix in Tag Cloud block
* Fix in Video block
* Fix in Global Styles Font Size
* Fix in Global Styles PushChangesToGlobalStylesControl
* Fix in new template part modal
* Fix in pattern overrides block inspector
* Add lint rule
* Update docs
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.eslintrc.js | 1 +
packages/block-library/src/gallery/edit.js | 5 +-
packages/block-library/src/search/edit.js | 18 ++--
packages/block-library/src/search/editor.scss | 7 +-
packages/block-library/src/tag-cloud/edit.js | 69 +++++++-------
.../block-library/src/tag-cloud/editor.scss | 8 ++
.../src/video/edit-common-settings.js | 1 +
packages/block-library/src/video/edit.js | 4 +-
.../components/src/base-control/README.md | 7 +-
.../components/src/base-control/index.tsx | 89 ++++++++++---------
.../global-styles/size-control/index.js | 8 +-
.../push-changes-to-global-styles/index.js | 1 +
.../create-template-part-modal/index.js | 1 +
.../components/pattern-overrides-controls.js | 1 +
14 files changed, 128 insertions(+), 92 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index cb669fb4177206..6143d62c28bc07 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -290,6 +290,7 @@ module.exports = {
...restrictedSyntax,
...restrictedSyntaxComponents,
...[
+ 'BaseControl',
'CheckboxControl',
'ComboboxControl',
'DimensionControl',
diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js
index 1a7ce5fae04f8c..c73fe1977ce4cc 100644
--- a/packages/block-library/src/gallery/edit.js
+++ b/packages/block-library/src/gallery/edit.js
@@ -602,7 +602,10 @@ export default function GalleryEdit( props ) {
/>
) }
{ Platform.isWeb && ! imageSizeOptions && hasImageIds && (
-
+
{ __( 'Resolution' ) }
diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js
index cfe7b29caf5de0..e2f3bb3999e42c 100644
--- a/packages/block-library/src/search/edit.js
+++ b/packages/block-library/src/search/edit.js
@@ -28,7 +28,7 @@ import {
ToolbarButton,
ResizableBox,
PanelBody,
- BaseControl,
+ __experimentalVStack as VStack,
__experimentalUseCustomUnits as useCustomUnits,
__experimentalUnitControl as UnitControl,
} from '@wordpress/components';
@@ -408,12 +408,14 @@ export default function SearchEdit( {
-
100
? 100
: newWidth;
-
setAttributes( {
width: parseInt( filteredWidth, 10 ),
} );
@@ -445,9 +446,8 @@ export default function SearchEdit( {
value={ `${ width }${ widthUnit }` }
units={ units }
/>
-
{ [ 25, 50, 75, 100 ].map( ( widthValue ) => {
@@ -473,7 +473,7 @@ export default function SearchEdit( {
);
} ) }
-
+
>
diff --git a/packages/block-library/src/search/editor.scss b/packages/block-library/src/search/editor.scss
index 35ccfc5e633fc2..ecc244d3341e1e 100644
--- a/packages/block-library/src/search/editor.scss
+++ b/packages/block-library/src/search/editor.scss
@@ -15,8 +15,11 @@
justify-content: center;
text-align: center;
}
+}
- &__components-button-group {
- margin-top: 10px;
+.wp-block-search__inspector-controls {
+ .components-base-control {
+ // Counteract the margin added by the block inspector.
+ margin-bottom: 0;
}
}
diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js
index 8ced99271e4807..eeb568e7a89ef1 100644
--- a/packages/block-library/src/tag-cloud/edit.js
+++ b/packages/block-library/src/tag-cloud/edit.js
@@ -11,8 +11,8 @@ import {
__experimentalUnitControl as UnitControl,
__experimentalUseCustomUnits as useCustomUnits,
__experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue,
+ __experimentalVStack as VStack,
Disabled,
- BaseControl,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
@@ -119,17 +119,20 @@ function TagCloudEdit( { attributes, setAttributes } ) {
const inspectorControls = (
-
- setAttributes( { taxonomy: selectedTaxonomy } )
- }
- />
-
+
+
+ setAttributes( { taxonomy: selectedTaxonomy } )
+ }
+ />
-
-
- setAttributes( { numberOfTags: value } )
- }
- min={ MIN_TAGS }
- max={ MAX_TAGS }
- required
- />
-
- setAttributes( { showTagCounts: ! showTagCounts } )
- }
- />
+
+ setAttributes( { numberOfTags: value } )
+ }
+ min={ MIN_TAGS }
+ max={ MAX_TAGS }
+ required
+ />
+
+ setAttributes( { showTagCounts: ! showTagCounts } )
+ }
+ />
+
);
diff --git a/packages/block-library/src/tag-cloud/editor.scss b/packages/block-library/src/tag-cloud/editor.scss
index d00a450174f2fd..e85129e22f1aca 100644
--- a/packages/block-library/src/tag-cloud/editor.scss
+++ b/packages/block-library/src/tag-cloud/editor.scss
@@ -9,3 +9,11 @@
border: none;
border-radius: inherit;
}
+
+.wp-block-tag-cloud__inspector-settings {
+ .components-base-control,
+ .components-base-control:last-child {
+ // Cancel out extra margins added by block inspector
+ margin-bottom: 0;
+ }
+}
diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js
index 5ebf431ade3fc0..9394bfaf5c6145 100644
--- a/packages/block-library/src/video/edit-common-settings.js
+++ b/packages/block-library/src/video/edit-common-settings.js
@@ -83,6 +83,7 @@ const VideoSettings = ( { setAttributes, attributes } ) => {
) }
/>
-
+
{ __( 'Poster image' ) }
@@ -265,7 +265,7 @@ function VideoEdit( {
{ __( 'Remove' ) }
) }
-
+
diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md
index dc3d8c0e29c8e0..d51629de6f7253 100644
--- a/packages/components/src/base-control/README.md
+++ b/packages/components/src/base-control/README.md
@@ -15,7 +15,7 @@ const MyCustomTextareaControl = ({ children, ...baseProps }) => (
const { baseControlProps, controlProps } = useBaseControlProps( baseProps );
return (
-
+
@@ -92,7 +92,10 @@ It should only be used in cases where the children being rendered inside BaseCon
import { BaseControl } from '@wordpress/components';
const MyBaseControl = () => (
-
+
Author
Select an author
diff --git a/packages/components/src/base-control/index.tsx b/packages/components/src/base-control/index.tsx
index 14ecce1bdd729d..77899b6480daed 100644
--- a/packages/components/src/base-control/index.tsx
+++ b/packages/components/src/base-control/index.tsx
@@ -26,29 +26,6 @@ import { contextConnectWithoutRef, useContextSystem } from '../context';
export { useBaseControlProps } from './hooks';
-/**
- * `BaseControl` is a component used to generate labels and help text for components handling user inputs.
- *
- * ```jsx
- * import { BaseControl, useBaseControlProps } from '@wordpress/components';
- *
- * // Render a `BaseControl` for a textarea input
- * const MyCustomTextareaControl = ({ children, ...baseProps }) => (
- * // `useBaseControlProps` is a convenience hook to get the props for the `BaseControl`
- * // and the inner control itself. Namely, it takes care of generating a unique `id`,
- * // properly associating it with the `label` and `help` elements.
- * const { baseControlProps, controlProps } = useBaseControlProps( baseProps );
- *
- * return (
- *
- *
- *
- * );
- * );
- * ```
- */
const UnconnectedBaseControl = (
props: WordPressComponentProps< BaseControlProps, null >
) => {
@@ -105,23 +82,6 @@ const UnconnectedBaseControl = (
);
};
-/**
- * `BaseControl.VisualLabel` is used to render a purely visual label inside a `BaseControl` component.
- *
- * It should only be used in cases where the children being rendered inside `BaseControl` are already accessibly labeled,
- * e.g., a button, but we want an additional visual label for that section equivalent to the labels `BaseControl` would
- * otherwise use if the `label` prop was passed.
- *
- * @example
- * import { BaseControl } from '@wordpress/components';
- *
- * const MyBaseControl = () => (
- *
- * Author
- * Select an author
- *
- * );
- */
const UnforwardedVisualLabel = (
props: WordPressComponentProps< BaseControlVisualLabelProps, 'span' >,
ref: ForwardedRef< any >
@@ -141,9 +101,56 @@ const UnforwardedVisualLabel = (
export const VisualLabel = forwardRef( UnforwardedVisualLabel );
+/**
+ * `BaseControl` is a component used to generate labels and help text for components handling user inputs.
+ *
+ * ```jsx
+ * import { BaseControl, useBaseControlProps } from '@wordpress/components';
+ *
+ * // Render a `BaseControl` for a textarea input
+ * const MyCustomTextareaControl = ({ children, ...baseProps }) => (
+ * // `useBaseControlProps` is a convenience hook to get the props for the `BaseControl`
+ * // and the inner control itself. Namely, it takes care of generating a unique `id`,
+ * // properly associating it with the `label` and `help` elements.
+ * const { baseControlProps, controlProps } = useBaseControlProps( baseProps );
+ *
+ * return (
+ *
+ *
+ *
+ * );
+ * );
+ * ```
+ */
export const BaseControl = Object.assign(
contextConnectWithoutRef( UnconnectedBaseControl, 'BaseControl' ),
- { VisualLabel }
+
+ {
+ /**
+ * `BaseControl.VisualLabel` is used to render a purely visual label inside a `BaseControl` component.
+ *
+ * It should only be used in cases where the children being rendered inside `BaseControl` are already accessibly labeled,
+ * e.g., a button, but we want an additional visual label for that section equivalent to the labels `BaseControl` would
+ * otherwise use if the `label` prop was passed.
+ *
+ * ```jsx
+ * import { BaseControl } from '@wordpress/components';
+ *
+ * const MyBaseControl = () => (
+ *
+ * Author
+ * Select an author
+ *
+ * );
+ * ```
+ */
+ VisualLabel,
+ }
);
export default BaseControl;
diff --git a/packages/edit-site/src/components/global-styles/size-control/index.js b/packages/edit-site/src/components/global-styles/size-control/index.js
index a7e7bd6127a5fb..28fa64c643cbbd 100644
--- a/packages/edit-site/src/components/global-styles/size-control/index.js
+++ b/packages/edit-site/src/components/global-styles/size-control/index.js
@@ -20,7 +20,11 @@ import {
const DEFAULT_UNITS = [ 'px', 'em', 'rem', 'vw', 'vh' ];
-function SizeControl( props ) {
+function SizeControl( {
+ // Do not allow manipulation of margin bottom
+ __nextHasNoMarginBottom,
+ ...props
+} ) {
const { baseControlProps } = useBaseControlProps( props );
const { value, onChange, fallbackValue, disabled } = props;
@@ -45,7 +49,7 @@ function SizeControl( props ) {
};
return (
-
+
Date: Mon, 12 Aug 2024 19:34:49 +0200
Subject: [PATCH 049/126] fix typo in block-filters.md (#64452)
https://github.com/WordPress/gutenberg/pull/64447#pullrequestreview-2233534636
---
docs/reference-guides/filters/block-filters.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/reference-guides/filters/block-filters.md b/docs/reference-guides/filters/block-filters.md
index c70bb356a445a8..637cecadf1402b 100644
--- a/docs/reference-guides/filters/block-filters.md
+++ b/docs/reference-guides/filters/block-filters.md
@@ -139,7 +139,7 @@ The following PHP filters are available to change the output of a block on the f
### `render_block`
-Filters the font-end content of any block. This filter has no impact on the behavior of blocks in the Editor.
+Filters the front-end content of any block. This filter has no impact on the behavior of blocks in the Editor.
The callback function for this filter receives three parameters:
@@ -172,7 +172,7 @@ add_filter( 'render_block', 'example_add_custom_class_to_paragraph_block', 10, 2
### `render_block_{namespace/block}`
-Filters the font-end content of the defined block. This is just a simpler form of `render_block` when you only need to modify a specific block type.
+Filters the front-end content of the defined block. This is just a simpler form of `render_block` when you only need to modify a specific block type.
The callback function for this filter receives three parameters:
From 47138724bf9e6abc67f448e3403428d431f2a1b9 Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Tue, 13 Aug 2024 13:33:25 +0900
Subject: [PATCH 050/126] In-between Inserter: Show inserter when it doesn't
conflict with block toolbar (#64229)
Unlinked contributors: digitalex11.
Co-authored-by: t-hamano
Co-authored-by: Mamaduka
Co-authored-by: ellatrix
Co-authored-by: annezazu
Co-authored-by: talldan
Co-authored-by: hanneslsm
---
.../block-list/use-in-between-inserter.js | 22 ++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js
index 74151fb3b070ba..bb307816fd1501 100644
--- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js
+++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js
@@ -25,6 +25,7 @@ export function useInBetweenInserter() {
getBlockIndex,
isMultiSelecting,
getSelectedBlockClientIds,
+ getSettings,
getTemplateLock,
__unstableIsWithinBlockOverlay,
getBlockEditingMode,
@@ -88,9 +89,11 @@ export function useInBetweenInserter() {
return;
}
+ const blockListSettings = getBlockListSettings( rootClientId );
const orientation =
- getBlockListSettings( rootClientId )?.orientation ||
- 'vertical';
+ blockListSettings?.orientation || 'vertical';
+ const captureToolbars =
+ !! blockListSettings?.__experimentalCaptureToolbars;
const offsetTop = event.clientY;
const offsetLeft = event.clientX;
@@ -135,9 +138,18 @@ export function useInBetweenInserter() {
return;
}
- // Don't show the inserter when hovering above (conflicts with
- // block toolbar) or inside selected block(s).
- if ( getSelectedBlockClientIds().includes( clientId ) ) {
+ // Don't show the inserter if the following conditions are met,
+ // as it conflicts with the block toolbar:
+ // 1. when hovering above or inside selected block(s)
+ // 2. when the orientation is vertical
+ // 3. when the __experimentalCaptureToolbars is not enabled
+ // 4. when the Top Toolbar is not disabled
+ if (
+ getSelectedBlockClientIds().includes( clientId ) &&
+ orientation === 'vertical' &&
+ ! captureToolbars &&
+ ! getSettings().hasFixedToolbar
+ ) {
return;
}
const elementRect = element.getBoundingClientRect();
From 344d61e5df74411283014bcbb5dbbed0b54f000a Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Tue, 13 Aug 2024 08:48:19 +0400
Subject: [PATCH 051/126] Block Editor: Use hooks instead of HoC in
'BlockModeToggle' (#64460)
Co-authored-by: Mamaduka
Co-authored-by: t-hamano
---
.../block-settings-menu/block-mode-toggle.js | 59 +++++++++----------
.../test/block-mode-toggle.js | 49 +++++++--------
2 files changed, 54 insertions(+), 54 deletions(-)
diff --git a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js
index 6810a21581f12e..7ca294a2894158 100644
--- a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js
+++ b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js
@@ -4,8 +4,7 @@
import { __ } from '@wordpress/i18n';
import { MenuItem } from '@wordpress/components';
import { getBlockType, hasBlockSupport } from '@wordpress/blocks';
-import { withSelect, withDispatch } from '@wordpress/data';
-import { compose } from '@wordpress/compose';
+import { useDispatch, useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -14,13 +13,23 @@ import { store as blockEditorStore } from '../../store';
const noop = () => {};
-export function BlockModeToggle( {
- blockType,
- mode,
- onToggleMode,
- small = false,
- isCodeEditingEnabled = true,
-} ) {
+export default function BlockModeToggle( { clientId, onToggle = noop } ) {
+ const { blockType, mode, isCodeEditingEnabled } = useSelect(
+ ( select ) => {
+ const { getBlock, getBlockMode, getSettings } =
+ select( blockEditorStore );
+ const block = getBlock( clientId );
+
+ return {
+ mode: getBlockMode( clientId ),
+ blockType: block ? getBlockType( block.name ) : null,
+ isCodeEditingEnabled: getSettings().codeEditingEnabled,
+ };
+ },
+ [ clientId ]
+ );
+ const { toggleBlockMode } = useDispatch( blockEditorStore );
+
if (
! blockType ||
! hasBlockSupport( blockType, 'html', true ) ||
@@ -32,26 +41,14 @@ export function BlockModeToggle( {
const label =
mode === 'visual' ? __( 'Edit as HTML' ) : __( 'Edit visually' );
- return { ! small && label } ;
+ return (
+ {
+ toggleBlockMode( clientId );
+ onToggle();
+ } }
+ >
+ { label }
+
+ );
}
-
-export default compose( [
- withSelect( ( select, { clientId } ) => {
- const { getBlock, getBlockMode, getSettings } =
- select( blockEditorStore );
- const block = getBlock( clientId );
- const isCodeEditingEnabled = getSettings().codeEditingEnabled;
-
- return {
- mode: getBlockMode( clientId ),
- blockType: block ? getBlockType( block.name ) : null,
- isCodeEditingEnabled,
- };
- } ),
- withDispatch( ( dispatch, { onToggle = noop, clientId } ) => ( {
- onToggleMode() {
- dispatch( blockEditorStore ).toggleBlockMode( clientId );
- onToggle();
- },
- } ) ),
-] )( BlockModeToggle );
diff --git a/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js
index c297bad15f29ea..67d88125e3429c 100644
--- a/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js
+++ b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js
@@ -3,16 +3,32 @@
*/
import { render, screen } from '@testing-library/react';
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+
/**
* Internal dependencies
*/
-import { BlockModeToggle } from '../block-mode-toggle';
+import BlockModeToggle from '../block-mode-toggle';
+
+jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
+
+function setupUseSelectMock( mode, blockType, codeEditingEnabled = true ) {
+ useSelect.mockImplementation( () => {
+ return {
+ mode,
+ blockType,
+ isCodeEditingEnabled: codeEditingEnabled,
+ };
+ } );
+}
describe( 'BlockModeToggle', () => {
it( "should not render the HTML mode button if the block doesn't support it", () => {
- render(
-
- );
+ setupUseSelectMock( undefined, { supports: { html: false } } );
+ render( );
expect(
screen.queryByRole( 'menuitem', { name: 'Edit as HTML' } )
@@ -20,12 +36,8 @@ describe( 'BlockModeToggle', () => {
} );
it( 'should render the HTML mode button', () => {
- render(
-
- );
+ setupUseSelectMock( 'visual', { supports: { html: true } } );
+ render( );
expect(
screen.getByRole( 'menuitem', { name: 'Edit as HTML' } )
@@ -33,12 +45,8 @@ describe( 'BlockModeToggle', () => {
} );
it( 'should render the Visual mode button', () => {
- render(
-
- );
+ setupUseSelectMock( 'html', { supports: { html: true } } );
+ render( );
expect(
screen.getByRole( 'menuitem', { name: 'Edit visually' } )
@@ -46,13 +54,8 @@ describe( 'BlockModeToggle', () => {
} );
it( 'should not render the Visual mode button if code editing is disabled', () => {
- render(
-
- );
+ setupUseSelectMock( 'html', { supports: { html: true } }, false );
+ render( );
expect(
screen.queryByRole( 'menuitem', { name: 'Edit visually' } )
From 6445ff13273b7bdbf5315c1394b5aeda8011e47c Mon Sep 17 00:00:00 2001
From: Robert Lee
Date: Mon, 12 Aug 2024 22:46:36 -0700
Subject: [PATCH 052/126] WPCompleter: Restrict block list to allowed blocks
only (#64413)
Unlinked contributors: ssang.
Co-authored-by: talldan
---
packages/block-editor/src/autocompleters/block.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/block-editor/src/autocompleters/block.js b/packages/block-editor/src/autocompleters/block.js
index bc06c9de5aaaff..859ae11036c82a 100644
--- a/packages/block-editor/src/autocompleters/block.js
+++ b/packages/block-editor/src/autocompleters/block.js
@@ -60,7 +60,8 @@ function createBlockCompleter() {
}, [] );
const [ items, categories, collections ] = useBlockTypesState(
rootClientId,
- noop
+ noop,
+ true
);
const filteredItems = useMemo( () => {
From 476c78b36e0ac1af0f1cbec75507a6b288c4df29 Mon Sep 17 00:00:00 2001
From: Daniel Richards
Date: Tue, 13 Aug 2024 14:15:23 +0800
Subject: [PATCH 053/126] Fix bumped specificity for layout styles in
non-iframed editor (#64076)
* Fix too specific layout styles in non-iframed editor
* Ensure first/last child rules take precedence
* Adjust selectors so that `> :first-child`/`> :last-child` still has 0,2,0 specificity to override theme.json spacing
* Update tests
* Update client side layout selectors to match theme json
* Add backport changelog
----
Co-authored-by: talldan
Co-authored-by: andrewserong
Co-authored-by: ramonjd
Co-authored-by: aaronrobertshaw
---
backport-changelog/6.6/7145.md | 3 +++
lib/class-wp-theme-json-gutenberg.php | 2 +-
.../global-styles/test/use-global-styles-output.js | 6 +++---
.../global-styles/use-global-styles-output.js | 4 ++--
phpunit/class-wp-theme-json-test.php | 11 +++++------
5 files changed, 14 insertions(+), 12 deletions(-)
create mode 100644 backport-changelog/6.6/7145.md
diff --git a/backport-changelog/6.6/7145.md b/backport-changelog/6.6/7145.md
new file mode 100644
index 00000000000000..386f765cb22fa8
--- /dev/null
+++ b/backport-changelog/6.6/7145.md
@@ -0,0 +1,3 @@
+https://github.com/WordPress/wordpress-develop/pull/7145
+
+* https://github.com/WordPress/gutenberg/pull/64076
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 7558438dcbe745..20ea31090407b4 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -1744,7 +1744,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) {
$spacing_rule['selector']
);
} else {
- $format = static::ROOT_BLOCK_SELECTOR === $selector ? '.%2$s %3$s' : '%1$s-%2$s %3$s';
+ $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s';
$layout_selector = sprintf(
$format,
$selector,
diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
index e2530fdb85f812..1b061f6921f2c2 100644
--- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
+++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
@@ -763,7 +763,7 @@ describe( 'global styles renderer', () => {
} );
expect( layoutStyles ).toEqual(
- '.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.is-layout-flow > * + * { margin-block-start: 0.5em; margin-block-end: 0; }.is-layout-flex { gap: 0.5em; }:root { --wp--style--block-gap: 0.5em; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }'
+ ':root :where(.is-layout-flow) > * { margin-block-start: 0; margin-block-end: 0; }:root :where(.is-layout-flow) > * + * { margin-block-start: 0.5em; margin-block-end: 0; }:root :where(.is-layout-flex) { gap: 0.5em; }:root { --wp--style--block-gap: 0.5em; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }'
);
} );
@@ -780,7 +780,7 @@ describe( 'global styles renderer', () => {
} );
expect( layoutStyles ).toEqual(
- '.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.is-layout-flex { gap: 12px; }:root { --wp--style--block-gap: 12px; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }'
+ ':root :where(.is-layout-flow) > * { margin-block-start: 0; margin-block-end: 0; }:root :where(.is-layout-flow) > * + * { margin-block-start: 12px; margin-block-end: 0; }:root :where(.is-layout-flex) { gap: 12px; }:root { --wp--style--block-gap: 12px; }.is-layout-flow > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.is-layout-flow > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.is-layout-flow > .aligncenter { margin-left: auto !important; margin-right: auto !important; }body .is-layout-flex { display:flex; }.is-layout-flex { flex-wrap: wrap; align-items: center; }.is-layout-flex > * { margin: 0; }'
);
} );
@@ -797,7 +797,7 @@ describe( 'global styles renderer', () => {
} );
expect( layoutStyles ).toEqual(
- '.wp-block-group-is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.wp-block-group-is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.wp-block-group-is-layout-flex { gap: 12px; }'
+ ':root :where(.wp-block-group-is-layout-flow) > * { margin-block-start: 0; margin-block-end: 0; }:root :where(.wp-block-group-is-layout-flow) > * + * { margin-block-start: 12px; margin-block-end: 0; }:root :where(.wp-block-group-is-layout-flex) { gap: 12px; }'
);
} );
diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js
index 9190733d5b6607..c1449d6d8b298f 100644
--- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js
+++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js
@@ -538,10 +538,10 @@ export function getLayoutStyles( {
} else {
combinedSelector =
selector === ROOT_BLOCK_SELECTOR
- ? `.${ className }${
+ ? `:root :where(.${ className })${
spacingStyle?.selector || ''
}`
- : `${ selector }-${ className }${
+ : `:root :where(${ selector }-${ className })${
spacingStyle?.selector || ''
}`;
}
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index d366e0df44c4a2..3f11dd97a6688f 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -714,7 +714,7 @@ public function test_get_stylesheet_renders_enabled_protected_properties() {
)
);
- $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-flex {gap: 1em;}.is-layout-grid {gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}';
+ $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1em;}:root :where(.is-layout-grid){gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}';
$this->assertSameCSS( $expected, $theme_json->get_stylesheet() );
$this->assertSameCSS( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) );
}
@@ -1131,7 +1131,7 @@ public function test_get_stylesheet_generates_layout_styles() {
// Results also include root site blocks styles.
$this->assertSameCSS(
- ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-flex {gap: 1em;}.is-layout-grid {gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}',
+ ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1em;}:root :where(.is-layout-grid){gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}',
$theme_json->get_stylesheet( array( 'styles' ) )
);
}
@@ -1160,7 +1160,7 @@ public function test_get_stylesheet_generates_layout_styles_with_spacing_presets
// Results also include root site blocks styles.
$this->assertSameCSS(
- ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-flex {gap: var(--wp--preset--spacing--60);}.is-layout-grid {gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}',
+ ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:root :where(.is-layout-flex){gap: var(--wp--preset--spacing--60);}:root :where(.is-layout-grid){gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}',
$theme_json->get_stylesheet( array( 'styles' ) )
);
}
@@ -1284,8 +1284,7 @@ public function test_get_stylesheet_generates_valid_block_gap_values_and_skips_n
);
$this->assertSameCSS(
- ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1rem; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1rem; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1rem;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1rem;margin-block-end: 0;}.is-layout-flex {gap: 1rem;}.is-layout-grid {gap: 1rem;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}' .
- ':root :where(.wp-block-post-content){color: gray;}.wp-block-social-links-is-layout-flow > :first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-flow > :last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > :first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-constrained > :last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-flex {gap: 0;}.wp-block-social-links-is-layout-grid {gap: 0;}.wp-block-buttons-is-layout-flow > :first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-flow > :last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > :first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-constrained > :last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-flex {gap: 0;}.wp-block-buttons-is-layout-grid {gap: 0;}',
+ ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1rem; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1rem; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1rem;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1rem;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1rem;}:root :where(.is-layout-grid){gap: 1rem;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(.wp-block-post-content){color: gray;}:root :where(.wp-block-social-links-is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.wp-block-social-links-is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-flow) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.wp-block-social-links-is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-constrained) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-flex){gap: 0;}:root :where(.wp-block-social-links-is-layout-grid){gap: 0;}:root :where(.wp-block-buttons-is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.wp-block-buttons-is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-flow) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-flex){gap: 0;}:root :where(.wp-block-buttons-is-layout-grid){gap: 0;}',
$theme_json->get_stylesheet()
);
}
@@ -3690,7 +3689,7 @@ public function test_get_styles_with_appearance_tools() {
'selector' => 'body',
);
- $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: ; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: ; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1;margin-block-end: 0;}.is-layout-flex {gap: 1;}.is-layout-grid {gap: 1;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}';
+ $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: ; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: ; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1;}:root :where(.is-layout-grid){gap: 1;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}';
$this->assertSameCSS( $expected, $theme_json->get_root_layout_rules( WP_Theme_JSON_Gutenberg::ROOT_BLOCK_SELECTOR, $metadata ) );
}
From 33093652ea76c4463e78252bb53f2f331a81610c Mon Sep 17 00:00:00 2001
From: Dave Smith
Date: Tue, 13 Aug 2024 08:29:48 +0100
Subject: [PATCH 054/126] Focus pattern inserter search when activating zoom
out inserter (#64396)
* Add new search focus option for inserter panel
* Try focusing when inserter clicked in Zoom Out
* Store search input ref in block editor settings
* Remove legacy selector
* Remove more legacy code
* Move all ref selectors to block editor
* Update packages/editor/src/components/inserter-sidebar/index.js
---------
Co-authored-by: Ben Dwyer
Co-authored-by: getdave
Co-authored-by: scruffian
---
.../block-tools/zoom-out-mode-inserters.js | 8 ++++-
.../src/components/inserter/library.js | 2 ++
.../src/components/inserter/menu.js | 34 ++++++++++++-------
.../src/store/private-selectors.js | 4 +++
packages/block-editor/src/store/reducer.js | 5 +++
5 files changed, 39 insertions(+), 14 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 bb044f9479c024..6f986ce90dc3bd 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
@@ -23,6 +23,7 @@ function ZoomOutModeInserters() {
sectionRootClientId,
selectedBlockClientId,
hoveredBlockClientId,
+ inserterSearchInputRef,
} = useSelect( ( select ) => {
const {
getSettings,
@@ -32,8 +33,11 @@ function ZoomOutModeInserters() {
getSelectedBlockClientId,
getHoveredBlockClientId,
isBlockInsertionPointVisible,
- } = select( blockEditorStore );
+ getInserterSearchInputRef,
+ } = unlock( select( blockEditorStore ) );
+
const { sectionRootClientId: root } = unlock( getSettings() );
+
return {
hasSelection: !! getSelectionStart().clientId,
blockInsertionPoint: getBlockInsertionPoint(),
@@ -44,6 +48,7 @@ function ZoomOutModeInserters() {
getSettings().__experimentalSetIsInserterOpened,
selectedBlockClientId: getSelectedBlockClientId(),
hoveredBlockClientId: getHoveredBlockClientId(),
+ inserterSearchInputRef: getInserterSearchInputRef(),
};
}, [] );
@@ -110,6 +115,7 @@ function ZoomOutModeInserters() {
showInsertionPoint( sectionRootClientId, index, {
operation: 'insert',
} );
+ inserterSearchInputRef?.current?.focus();
} }
/>
) }
diff --git a/packages/block-editor/src/components/inserter/library.js b/packages/block-editor/src/components/inserter/library.js
index 4e10a051996a9f..fe14d48bb4016b 100644
--- a/packages/block-editor/src/components/inserter/library.js
+++ b/packages/block-editor/src/components/inserter/library.js
@@ -27,6 +27,7 @@ function InserterLibrary(
onSelect = noop,
shouldFocusBlock = false,
onClose,
+ __experimentalSearchInputRef,
},
ref
) {
@@ -58,6 +59,7 @@ function InserterLibrary(
shouldFocusBlock={ shouldFocusBlock }
ref={ ref }
onClose={ onClose }
+ __experimentalSearchInputRef={ __experimentalSearchInputRef }
/>
);
}
diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js
index 52e4e3062e9024..c5f41f9e3bf0a0 100644
--- a/packages/block-editor/src/components/inserter/menu.js
+++ b/packages/block-editor/src/components/inserter/menu.js
@@ -33,6 +33,7 @@ import useInsertionPoint from './hooks/use-insertion-point';
import { store as blockEditorStore } from '../../store';
import TabbedSidebar from '../tabbed-sidebar';
import { useZoomOut } from '../../hooks/use-zoom-out';
+import { unlock } from '../../lock-unlock';
const NOOP = () => {};
function InserterMenu(
@@ -53,11 +54,16 @@ function InserterMenu(
},
ref
) {
- const isZoomOutMode = useSelect(
- ( select ) =>
- select( blockEditorStore ).__unstableGetEditorMode() === 'zoom-out',
- []
- );
+ const { isZoomOutMode, inserterSearchInputRef } = useSelect( ( select ) => {
+ const { __unstableGetEditorMode, getInserterSearchInputRef } = unlock(
+ select( blockEditorStore )
+ );
+ return {
+ isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
+ inserterSearchInputRef: getInserterSearchInputRef(),
+ };
+ }, [] );
+
const [ filterValue, setFilterValue, delayedFilterValue ] =
useDebouncedInput( __experimentalFilterValue );
const [ hoveredItem, setHoveredItem ] = useState( null );
@@ -104,7 +110,7 @@ function InserterMenu(
}
} );
},
- [ onInsertBlocks, onSelect, shouldFocusBlock ]
+ [ onInsertBlocks, onSelect, ref, shouldFocusBlock ]
);
const onInsertPattern = useCallback(
@@ -113,7 +119,7 @@ function InserterMenu(
onInsertBlocks( blocks, { patternName } );
onSelect();
},
- [ onInsertBlocks, onSelect ]
+ [ onInsertBlocks, onSelect, onToggleInsertionPoint ]
);
const onHover = useCallback(
@@ -164,7 +170,9 @@ function InserterMenu(
value={ filterValue }
label={ __( 'Search for blocks and patterns' ) }
placeholder={ __( 'Search' ) }
+ ref={ inserterSearchInputRef }
/>
+
{ !! delayedFilterValue && (
{
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index c534c65b8defe7..dcd315a0ae2804 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -511,3 +511,7 @@ export function getTemporarilyEditingAsBlocks( state ) {
export function getTemporarilyEditingFocusModeToRevert( state ) {
return state.temporarilyEditingFocusModeRevert;
}
+
+export function getInserterSearchInputRef( state ) {
+ return state.inserterSearchInputRef;
+}
diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index 91d853a50a6a42..d9352670776371 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -2087,6 +2087,10 @@ export function hoveredBlockClientId( state = false, action ) {
return state;
}
+export function inserterSearchInputRef( state = { current: null } ) {
+ return state;
+}
+
const combinedReducers = combineReducers( {
blocks,
isDragging,
@@ -2120,6 +2124,7 @@ const combinedReducers = combineReducers( {
openedBlockSettingsMenu,
registeredInserterMediaCategories,
hoveredBlockClientId,
+ inserterSearchInputRef,
} );
function withAutomaticChangeReset( reducer ) {
From 27e441209db11f384e484f5a71453143c64c9b4a Mon Sep 17 00:00:00 2001
From: Daniel Richards
Date: Tue, 13 Aug 2024 16:00:20 +0800
Subject: [PATCH 055/126] Update postcss-prefixwrap dependency to 1.51.0 to fix
prefixing in `:where` selectors (#64458)
* Update postcss-prefixwrap dependency to 1.51.0 to fix prefixing in :where selectors
* Add extra test for :where with a pseudo selector
----
Co-authored-by: talldan
Co-authored-by: aaronrobertshaw
Co-authored-by: andrewserong
Co-authored-by: andreiglingeanu
---
package-lock.json | 31 ++++++------
packages/block-editor/package.json | 2 +-
.../src/utils/test/transform-styles.js | 49 +++++++++++++++++++
3 files changed, 66 insertions(+), 16 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 66c32076d3b364..471d7b845d24f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41754,14 +41754,6 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
- "node_modules/postcss-prefixwrap": {
- "version": "1.41.0",
- "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.41.0.tgz",
- "integrity": "sha512-gmwwAEE+ci3/ZKjUZppTETINlh1QwihY8gCstInuS7ohk353KYItU4d64hvnUvO2GUy29hBGPHz4Ce+qJRi90A==",
- "peerDependencies": {
- "postcss": "*"
- }
- },
"node_modules/postcss-reduce-initial": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz",
@@ -52237,7 +52229,7 @@
"fast-deep-equal": "^3.1.3",
"memize": "^2.1.0",
"postcss": "^8.4.21",
- "postcss-prefixwrap": "^1.41.0",
+ "postcss-prefixwrap": "^1.51.0",
"postcss-urlrebase": "^1.4.0",
"react-autosize-textarea": "^7.1.0",
"react-easy-crop": "^5.0.6",
@@ -52252,6 +52244,15 @@
"react-dom": "^18.0.0"
}
},
+ "packages/block-editor/node_modules/postcss-prefixwrap": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.51.0.tgz",
+ "integrity": "sha512-PuP4md5zFSY921dUcLShwSLv2YyyDec0dK9/puXl/lu7ZNvJ1U59+ZEFRMS67xwfNg5nIIlPXnAycPJlhA/Isw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "postcss": "*"
+ }
+ },
"packages/block-editor/node_modules/postcss-urlrebase": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.4.0.tgz",
@@ -67255,13 +67256,18 @@
"fast-deep-equal": "^3.1.3",
"memize": "^2.1.0",
"postcss": "^8.4.21",
- "postcss-prefixwrap": "^1.41.0",
+ "postcss-prefixwrap": "^1.51.0",
"postcss-urlrebase": "^1.4.0",
"react-autosize-textarea": "^7.1.0",
"react-easy-crop": "^5.0.6",
"remove-accents": "^0.5.0"
},
"dependencies": {
+ "postcss-prefixwrap": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.51.0.tgz",
+ "integrity": "sha512-PuP4md5zFSY921dUcLShwSLv2YyyDec0dK9/puXl/lu7ZNvJ1U59+ZEFRMS67xwfNg5nIIlPXnAycPJlhA/Isw=="
+ },
"postcss-urlrebase": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/postcss-urlrebase/-/postcss-urlrebase-1.4.0.tgz",
@@ -87820,11 +87826,6 @@
}
}
},
- "postcss-prefixwrap": {
- "version": "1.41.0",
- "resolved": "https://registry.npmjs.org/postcss-prefixwrap/-/postcss-prefixwrap-1.41.0.tgz",
- "integrity": "sha512-gmwwAEE+ci3/ZKjUZppTETINlh1QwihY8gCstInuS7ohk353KYItU4d64hvnUvO2GUy29hBGPHz4Ce+qJRi90A=="
- },
"postcss-reduce-initial": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz",
diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json
index 8ccaee6f0a955c..02765376e314b6 100644
--- a/packages/block-editor/package.json
+++ b/packages/block-editor/package.json
@@ -73,7 +73,7 @@
"fast-deep-equal": "^3.1.3",
"memize": "^2.1.0",
"postcss": "^8.4.21",
- "postcss-prefixwrap": "^1.41.0",
+ "postcss-prefixwrap": "^1.51.0",
"postcss-urlrebase": "^1.4.0",
"react-autosize-textarea": "^7.1.0",
"react-easy-crop": "^5.0.6",
diff --git a/packages/block-editor/src/utils/test/transform-styles.js b/packages/block-editor/src/utils/test/transform-styles.js
index 8245ce62831078..b0c6ca48deb355 100644
--- a/packages/block-editor/src/utils/test/transform-styles.js
+++ b/packages/block-editor/src/utils/test/transform-styles.js
@@ -125,6 +125,21 @@ describe( 'transformStyles', () => {
expect( output ).toMatchSnapshot();
} );
+ it( `should not try to replace 'body' in the middle of a classname`, () => {
+ const prefix = '.my-namespace';
+ const input = `.has-body-text { color: red; }`;
+ const output = transformStyles(
+ [
+ {
+ css: input,
+ },
+ ],
+ prefix
+ );
+
+ expect( output ).toEqual( [ `${ prefix } ${ input }` ] );
+ } );
+
it( 'should ignore keyframes', () => {
const input = `
@keyframes edit-post__fade-in-animation {
@@ -210,6 +225,40 @@ describe( 'transformStyles', () => {
expect( output ).toMatchSnapshot();
} );
+
+ it( 'should not try to wrap items within `:where` selectors', () => {
+ const input = `:where(.wp-element-button:active, .wp-block-button__link:active) { color: blue; }`;
+ const prefix = '.my-namespace';
+ const expected = [ `${ prefix } ${ input }` ];
+
+ const output = transformStyles(
+ [
+ {
+ css: input,
+ },
+ ],
+ prefix
+ );
+
+ expect( output ).toEqual( expected );
+ } );
+
+ it( 'should not try to prefix pseudo elements on `:where` selectors', () => {
+ const input = `:where(.wp-element-button, .wp-block-button__link)::before { color: blue; }`;
+ const prefix = '.my-namespace';
+ const expected = [ `${ prefix } ${ input }` ];
+
+ const output = transformStyles(
+ [
+ {
+ css: input,
+ },
+ ],
+ prefix
+ );
+
+ expect( output ).toEqual( expected );
+ } );
} );
it( 'should not break with data urls', () => {
From 926e7389a95701c3eba4702d4b4ef7f5d4325ffb Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Tue, 13 Aug 2024 10:25:34 +0200
Subject: [PATCH 056/126] DataViews Extensibility: Allow unregistering the
duplicate post action (#64441)
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
Co-authored-by: Mamaduka
Co-authored-by: sirreal
Co-authored-by: jsnajdr
---
.../src/components/post-actions/actions.js | 219 +-----------------
.../actions/duplicate-post.native.tsx | 3 +
.../src/dataviews/actions/duplicate-post.tsx | 174 ++++++++++++++
.../src/dataviews/actions/reorder-page.tsx | 6 +-
packages/editor/src/dataviews/fields/index.ts | 7 +-
.../src/dataviews/store/private-actions.ts | 9 +
packages/editor/src/dataviews/types.ts | 26 ++-
7 files changed, 213 insertions(+), 231 deletions(-)
create mode 100644 packages/editor/src/dataviews/actions/duplicate-post.native.tsx
create mode 100644 packages/editor/src/dataviews/actions/duplicate-post.tsx
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 0df73d1996deb5..627de5e8652e47 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -4,50 +4,15 @@
import { external } from '@wordpress/icons';
import { addQueryArgs } from '@wordpress/url';
import { useDispatch, useSelect } from '@wordpress/data';
-import { decodeEntities } from '@wordpress/html-entities';
import { store as coreStore } from '@wordpress/core-data';
-import { __, sprintf, _x } from '@wordpress/i18n';
-import { store as noticesStore } from '@wordpress/notices';
-import { useMemo, useState, useEffect } from '@wordpress/element';
-import { DataForm } from '@wordpress/dataviews';
-import {
- Button,
- __experimentalHStack as HStack,
- __experimentalVStack as VStack,
-} from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+import { useMemo, useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
-import {
- TEMPLATE_PART_POST_TYPE,
- TEMPLATE_POST_TYPE,
- PATTERN_POST_TYPE,
-} from '../../store/constants';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
-import { getItemTitle } from '../../dataviews/actions/utils';
-
-// TODO: this should be shared with other components (see post-fields in edit-site).
-const fields = [
- {
- type: 'text',
- 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 formDuplicateAction = {
- fields: [ 'title' ],
-};
const viewPostAction = {
id: 'view-post',
@@ -100,172 +65,6 @@ const postRevisionsAction = {
},
};
-const useDuplicatePostAction = ( postType ) => {
- const userCanCreatePost = useSelect(
- ( select ) => {
- return select( coreStore ).canUser( 'create', {
- kind: 'postType',
- name: postType,
- } );
- },
- [ postType ]
- );
- return useMemo(
- () =>
- userCanCreatePost && {
- id: 'duplicate-post',
- label: _x( 'Duplicate', 'action label' ),
- isEligible( { status } ) {
- return status !== 'trash';
- },
- RenderModal: ( { items, closeModal, onActionPerformed } ) => {
- const [ item, setItem ] = useState( {
- ...items[ 0 ],
- title: sprintf(
- /* translators: %s: Existing template title */
- __( '%s (Copy)' ),
- getItemTitle( items[ 0 ] )
- ),
- } );
-
- const [ isCreatingPage, setIsCreatingPage ] =
- useState( false );
-
- const { saveEntityRecord } = useDispatch( coreStore );
- const { createSuccessNotice, createErrorNotice } =
- useDispatch( noticesStore );
-
- async function createPage( event ) {
- event.preventDefault();
-
- if ( isCreatingPage ) {
- return;
- }
-
- const newItemOject = {
- status: 'draft',
- title: item.title,
- slug: item.title || __( 'No title' ),
- comment_status: item.comment_status,
- content:
- typeof item.content === 'string'
- ? item.content
- : item.content.raw,
- excerpt: item.excerpt.raw,
- meta: item.meta,
- parent: item.parent,
- password: item.password,
- template: item.template,
- format: item.format,
- featured_media: item.featured_media,
- menu_order: item.menu_order,
- ping_status: item.ping_status,
- };
- const assignablePropertiesPrefix = 'wp:action-assign-';
- // Get all the properties that the current user is able to assign normally author, categories, tags,
- // and custom taxonomies.
- const assignableProperties = Object.keys(
- item?._links || {}
- )
- .filter( ( property ) =>
- property.startsWith(
- assignablePropertiesPrefix
- )
- )
- .map( ( property ) =>
- property.slice(
- assignablePropertiesPrefix.length
- )
- );
- assignableProperties.forEach( ( property ) => {
- if ( item[ property ] ) {
- newItemOject[ property ] = item[ property ];
- }
- } );
- setIsCreatingPage( true );
- try {
- const newItem = await saveEntityRecord(
- 'postType',
- item.type,
- newItemOject,
- { throwOnError: true }
- );
-
- createSuccessNotice(
- sprintf(
- // translators: %s: Title of the created template e.g: "Category".
- __( '"%s" successfully created.' ),
- decodeEntities(
- newItem.title?.rendered || item.title
- )
- ),
- {
- id: 'duplicate-post-action',
- type: 'snackbar',
- }
- );
-
- if ( onActionPerformed ) {
- onActionPerformed( [ newItem ] );
- }
- } catch ( error ) {
- const errorMessage =
- error.message && error.code !== 'unknown_error'
- ? error.message
- : __(
- 'An error occurred while duplicating the page.'
- );
-
- createErrorNotice( errorMessage, {
- type: 'snackbar',
- } );
- } finally {
- setIsCreatingPage( false );
- closeModal();
- }
- }
-
- return (
-
- );
- },
- },
- [ userCanCreatePost ]
- );
-};
-
export function usePostActions( { postType, onActionPerformed, context } ) {
const { defaultActions, postTypeObject } = useSelect(
( select ) => {
@@ -284,12 +83,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
registerPostTypeActions( postType );
}, [ registerPostTypeActions, postType ] );
- const duplicatePostAction = useDuplicatePostAction( postType );
- const isTemplateOrTemplatePart = [
- TEMPLATE_POST_TYPE,
- TEMPLATE_PART_POST_TYPE,
- ].includes( postType );
- const isPattern = postType === PATTERN_POST_TYPE;
const isLoaded = !! postTypeObject;
const supportsRevisions = !! postTypeObject?.supports?.revisions;
return useMemo( () => {
@@ -300,11 +93,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
let actions = [
postTypeObject?.viewable && viewPostAction,
supportsRevisions && postRevisionsAction,
- globalThis.IS_GUTENBERG_PLUGIN
- ? ! isTemplateOrTemplatePart &&
- ! isPattern &&
- duplicatePostAction
- : false,
...defaultActions,
].filter( Boolean );
// Filter actions based on provided context. If not provided
@@ -370,10 +158,7 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
return actions;
}, [
defaultActions,
- isTemplateOrTemplatePart,
- isPattern,
postTypeObject?.viewable,
- duplicatePostAction,
onActionPerformed,
isLoaded,
supportsRevisions,
diff --git a/packages/editor/src/dataviews/actions/duplicate-post.native.tsx b/packages/editor/src/dataviews/actions/duplicate-post.native.tsx
new file mode 100644
index 00000000000000..5468aa649abbd4
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/duplicate-post.native.tsx
@@ -0,0 +1,3 @@
+const duplicatePost = undefined;
+
+export default duplicatePost;
diff --git a/packages/editor/src/dataviews/actions/duplicate-post.tsx b/packages/editor/src/dataviews/actions/duplicate-post.tsx
new file mode 100644
index 00000000000000..0979d30da39519
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/duplicate-post.tsx
@@ -0,0 +1,174 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { decodeEntities } from '@wordpress/html-entities';
+import { store as coreStore } from '@wordpress/core-data';
+import { __, sprintf, _x } from '@wordpress/i18n';
+import { store as noticesStore } from '@wordpress/notices';
+import { useState } from '@wordpress/element';
+import { DataForm } from '@wordpress/dataviews';
+import {
+ Button,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
+} from '@wordpress/components';
+import type { Action } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+import { getItemTitle } from '../../dataviews/actions/utils';
+import type { CoreDataError, BasePost } from '../types';
+import { titleField } from '../fields';
+
+const fields = [ titleField ];
+const formDuplicateAction = {
+ fields: [ 'title' ],
+};
+
+const duplicatePost: Action< BasePost > = {
+ id: 'duplicate-post',
+ label: _x( 'Duplicate', 'action label' ),
+ isEligible( { status } ) {
+ return status !== 'trash';
+ },
+ RenderModal: ( { items, closeModal, onActionPerformed } ) => {
+ const [ item, setItem ] = useState< BasePost >( {
+ ...items[ 0 ],
+ title: sprintf(
+ /* translators: %s: Existing template title */
+ __( '%s (Copy)' ),
+ getItemTitle( items[ 0 ] )
+ ),
+ } );
+
+ const [ isCreatingPage, setIsCreatingPage ] = useState( false );
+ const { saveEntityRecord } = useDispatch( coreStore );
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+
+ async function createPage( event: React.FormEvent ) {
+ event.preventDefault();
+
+ if ( isCreatingPage ) {
+ return;
+ }
+
+ const newItemOject = {
+ status: 'draft',
+ title: item.title,
+ slug: item.title || __( 'No title' ),
+ comment_status: item.comment_status,
+ content:
+ typeof item.content === 'string'
+ ? item.content
+ : item.content.raw,
+ excerpt:
+ typeof item.excerpt === 'string'
+ ? item.excerpt
+ : item.excerpt?.raw,
+ meta: item.meta,
+ parent: item.parent,
+ password: item.password,
+ template: item.template,
+ format: item.format,
+ featured_media: item.featured_media,
+ menu_order: item.menu_order,
+ ping_status: item.ping_status,
+ };
+ const assignablePropertiesPrefix = 'wp:action-assign-';
+ // Get all the properties that the current user is able to assign normally author, categories, tags,
+ // and custom taxonomies.
+ const assignableProperties = Object.keys( item?._links || {} )
+ .filter( ( property ) =>
+ property.startsWith( assignablePropertiesPrefix )
+ )
+ .map( ( property ) =>
+ property.slice( assignablePropertiesPrefix.length )
+ );
+ assignableProperties.forEach( ( property ) => {
+ if ( item.hasOwnProperty( property ) ) {
+ // @ts-ignore
+ newItemOject[ property ] = item[ property ];
+ }
+ } );
+ setIsCreatingPage( true );
+ try {
+ const newItem = await saveEntityRecord(
+ 'postType',
+ item.type,
+ newItemOject,
+ { throwOnError: true }
+ );
+
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: Title of the created template e.g: "Category".
+ __( '"%s" successfully created.' ),
+ decodeEntities( newItem.title?.rendered || item.title )
+ ),
+ {
+ id: 'duplicate-post-action',
+ type: 'snackbar',
+ }
+ );
+
+ if ( onActionPerformed ) {
+ onActionPerformed( [ newItem ] );
+ }
+ } catch ( error ) {
+ const typedError = error as CoreDataError;
+ const errorMessage =
+ typedError.message && typedError.code !== 'unknown_error'
+ ? typedError.message
+ : __( 'An error occurred while duplicating the page.' );
+
+ createErrorNotice( errorMessage, {
+ type: 'snackbar',
+ } );
+ } finally {
+ setIsCreatingPage( false );
+ closeModal?.();
+ }
+ }
+
+ return (
+
+ );
+ },
+};
+
+export default duplicatePost;
diff --git a/packages/editor/src/dataviews/actions/reorder-page.tsx b/packages/editor/src/dataviews/actions/reorder-page.tsx
index 1b9524123e7c95..1820884d8d8c73 100644
--- a/packages/editor/src/dataviews/actions/reorder-page.tsx
+++ b/packages/editor/src/dataviews/actions/reorder-page.tsx
@@ -17,7 +17,7 @@ import type { Action, RenderModalProps } from '@wordpress/dataviews';
/**
* Internal dependencies
*/
-import type { CoreDataError, PostWithPageAttributesSupport } from '../types';
+import type { CoreDataError, BasePost } from '../types';
import { orderField } from '../fields';
const fields = [ orderField ];
@@ -29,7 +29,7 @@ function ReorderModal( {
items,
closeModal,
onActionPerformed,
-}: RenderModalProps< PostWithPageAttributesSupport > ) {
+}: RenderModalProps< BasePost > ) {
const [ item, setItem ] = useState( items[ 0 ] );
const orderInput = item.menu_order;
const { editEntityRecord, saveEditedEntityRecord } =
@@ -113,7 +113,7 @@ function ReorderModal( {
);
}
-const reorderPage: Action< PostWithPageAttributesSupport > = {
+const reorderPage: Action< BasePost > = {
id: 'order-pages',
label: __( 'Order' ),
isEligible( { status } ) {
diff --git a/packages/editor/src/dataviews/fields/index.ts b/packages/editor/src/dataviews/fields/index.ts
index ea30d15dab600b..b215172eaf7f02 100644
--- a/packages/editor/src/dataviews/fields/index.ts
+++ b/packages/editor/src/dataviews/fields/index.ts
@@ -7,17 +7,18 @@ import type { Field } from '@wordpress/dataviews';
/**
* Internal dependencies
*/
-import type { BasePost, PostWithPageAttributesSupport } from '../types';
+import type { BasePost } from '../types';
+import { getItemTitle } from '../actions/utils';
export const titleField: Field< BasePost > = {
type: 'text',
id: 'title',
label: __( 'Title' ),
placeholder: __( 'No title' ),
- getValue: ( { item } ) => item.title,
+ getValue: ( { item } ) => getItemTitle( item ),
};
-export const orderField: Field< PostWithPageAttributesSupport > = {
+export const orderField: Field< BasePost > = {
type: 'integer',
id: 'menu_order',
label: __( 'Order' ),
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index 80449d1b7a0798..d5b299b012e364 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -21,6 +21,7 @@ import restorePost from '../actions/restore-post';
import type { PostType } from '../types';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
+import duplicatePost from '../actions/duplicate-post';
export function registerEntityAction< Item >(
kind: string,
@@ -87,6 +88,14 @@ export const registerPostTypeActions =
.getCurrentTheme();
const actions = [
+ // @ts-ignore
+ globalThis.IS_GUTENBERG_PLUGIN
+ ? ! [ 'wp_template', 'wp_block', 'wp_template_part' ].includes(
+ postTypeConfig.slug
+ ) &&
+ canCreate &&
+ duplicatePost
+ : undefined,
postTypeConfig.slug === 'wp_template_part' &&
canCreate &&
currentTheme?.is_block_theme &&
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
index 5750ab96eeae81..514953d6691290 100644
--- a/packages/editor/src/dataviews/types.ts
+++ b/packages/editor/src/dataviews/types.ts
@@ -7,7 +7,7 @@ type PostStatus =
| 'auto-draft'
| 'trash';
-export interface BasePost {
+export interface CommonPost {
status?: PostStatus;
title: string | { rendered: string } | { raw: string };
content: string | { raw: string; rendered: string };
@@ -16,7 +16,21 @@ export interface BasePost {
blocks?: Object[];
}
-export interface Template extends BasePost {
+export interface BasePost extends CommonPost {
+ comment_status?: 'open' | 'closed';
+ excerpt?: string | { raw: string; rendered: string };
+ meta?: Record< string, any >;
+ parent?: number;
+ password?: string;
+ template?: string;
+ format?: string;
+ featured_media?: number;
+ menu_order?: number;
+ ping_status?: 'open' | 'closed';
+ _links?: Record< string, { href: string }[] >;
+}
+
+export interface Template extends CommonPost {
type: 'wp_template';
is_custom: boolean;
source: string;
@@ -24,7 +38,7 @@ export interface Template extends BasePost {
id: string;
}
-export interface TemplatePart extends BasePost {
+export interface TemplatePart extends CommonPost {
type: 'wp_template_part';
source: string;
has_theme_file: boolean;
@@ -32,16 +46,12 @@ export interface TemplatePart extends BasePost {
area: string;
}
-export interface Pattern extends BasePost {
+export interface Pattern extends CommonPost {
slug: string;
title: { raw: string };
wp_pattern_sync_status: string;
}
-export interface PostWithPageAttributesSupport extends BasePost {
- menu_order: number;
-}
-
export type Post = Template | TemplatePart | Pattern | BasePost;
export type PostWithPermissions = Post & {
From f9268debccb0fb53bb7552dd5723cece5c1178a2 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Tue, 13 Aug 2024 11:00:12 +0200
Subject: [PATCH 057/126] `Composite`: export `useCompositeStore`, add more
focus-related props (#64450)
* Export useCompositeStore from package
* Add `accessibleWhenDisabled` prop to `Composite.Item`
* Add `focusable`, `disabled`, `accessibleWhenDisabled`, and `onFocusVisible` props to `Composite`
* CHANGELOG
---
Co-authored-by: ciampo
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/composite/README.md | 67 +++++++++++++++++
packages/components/src/composite/index.tsx | 6 +-
.../src/composite/stories/index.story.tsx | 71 ++++++++++++++++++-
packages/components/src/composite/types.ts | 67 +++++++++++++++++
packages/components/src/index.ts | 2 +-
6 files changed, 210 insertions(+), 4 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 57ba69197cfa98..761e6604a127a2 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -6,6 +6,7 @@
- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).
- `Composite`: add `Hover` and `Typeahead` subcomponents ([#64399](https://github.com/WordPress/gutenberg/pull/64399)).
+- `Composite`: export `useCompositeStore, add focus-related props to `Composite`and`Composite.Item` subcomponents ([#64450](https://github.com/WordPress/gutenberg/pull/64450)).
### Enhancements
diff --git a/packages/components/src/composite/README.md b/packages/components/src/composite/README.md
index 7bd12d0cabfa0c..3670e31b01e9df 100644
--- a/packages/components/src/composite/README.md
+++ b/packages/components/src/composite/README.md
@@ -147,6 +147,55 @@ Allows the component to be rendered as a different HTML element or React compone
- Required: no
+##### `focusable`: `boolean`
+
+Makes the component a focusable element. When this element gains keyboard focus, it gets a `data-focus-visible` attribute and triggers the `onFocusVisible` prop.
+
+The component supports the `disabled` prop even for those elements not supporting the native `disabled` attribute. Disabled elements may be still accessible via keyboard by using the the `accessibleWhenDisabled` prop.
+
+Non-native focusable elements will lose their focusability entirely. However, native focusable elements will retain their inherent focusability.
+
+- Required: no
+
+##### `disabled`: `boolean`
+
+Determines if the element is disabled. This sets the `aria-disabled` attribute accordingly, enabling support for all elements, including those that don't support the native `disabled` attribute.
+
+This feature can be combined with the `accessibleWhenDisabled` prop to
+make disabled elements still accessible via keyboard.
+
+**Note**: For this prop to work, the `focusable` prop must be set to
+`true`, if it's not set by default.
+
+- Required: no
+- Default: `false`
+
+##### `accessibleWhenDisabled`: `boolean`
+
+Indicates whether the element should be focusable even when it is
+`disabled`.
+
+This is important when discoverability is a concern. For example:
+
+> A toolbar in an editor contains a set of special smart paste functions
+> that are disabled when the clipboard is empty or when the function is not
+> applicable to the current content of the clipboard. It could be helpful to
+> keep the disabled buttons focusable if the ability to discover their
+> functionality is primarily via their presence on the toolbar.
+
+Learn more on [Focusability of disabled
+controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols).
+
+- Required: no
+
+##### `onFocusVisible`: `(event: SyntheticEvent) => void`
+
+Custom event handler invoked when the element gains focus through keyboard interaction or a key press occurs while the element is in focus. This is the programmatic equivalent of the `data-focus-visible` attribute.
+
+**Note**: For this prop to work, the `focusable` prop must be set to `true` if it's not set by default.
+
+- Required: no
+
##### `children`: `React.ReactNode`
The contents of the component.
@@ -189,6 +238,24 @@ The contents of the component.
Renders a composite item.
+##### `accessibleWhenDisabled`: `boolean`
+
+Indicates whether the element should be focusable even when it is
+`disabled`.
+
+This is important when discoverability is a concern. For example:
+
+> A toolbar in an editor contains a set of special smart paste functions
+> that are disabled when the clipboard is empty or when the function is not
+> applicable to the current content of the clipboard. It could be helpful to
+> keep the disabled buttons focusable if the ability to discover their
+> functionality is primarily via their presence on the toolbar.
+
+Learn more on [Focusability of disabled
+controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols).
+
+- Required: no
+
##### `render`: `RenderProp & { ref?: React.Ref | undefined; }> | React.ReactElement>`
Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.
diff --git a/packages/components/src/composite/index.tsx b/packages/components/src/composite/index.tsx
index 4e87b9a55fa5bb..f5d92330cada3c 100644
--- a/packages/components/src/composite/index.tsx
+++ b/packages/components/src/composite/index.tsx
@@ -136,8 +136,10 @@ export const Composite = Object.assign(
forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeProps, 'div', false >
- >( function CompositeRow( props, ref ) {
- return ;
+ >( function Composite( { disabled = false, ...props }, ref ) {
+ return (
+
+ );
} ),
{
displayName: 'Composite',
diff --git a/packages/components/src/composite/stories/index.story.tsx b/packages/components/src/composite/stories/index.story.tsx
index f1be53445f79ad..405962b92a761c 100644
--- a/packages/components/src/composite/stories/index.story.tsx
+++ b/packages/components/src/composite/stories/index.story.tsx
@@ -74,6 +74,28 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
table: { type: { summary: 'React.ReactNode' } },
},
};
+ const accessibleWhenDisabled = {
+ name: 'accessibleWhenDisabled',
+ description: `Indicates whether the element should be focusable even when it is
+\`disabled\`.
+
+This is important when discoverability is a concern. For example:
+
+> A toolbar in an editor contains a set of special smart paste functions
+> that are disabled when the clipboard is empty or when the function is not
+> applicable to the current content of the clipboard. It could be helpful to
+> keep the disabled buttons focusable if the ability to discover their
+> functionality is primarily via their presence on the toolbar.
+
+Learn more on [Focusability of disabled
+controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols).`,
+ table: {
+ type: {
+ summary: 'boolean',
+ },
+ },
+ };
+
const argTypes = {
useCompositeStore: {
activeId: {
@@ -226,11 +248,58 @@ This only affects the composite widget behavior. You still need to set \`dir="rt
},
type: { required: true },
},
+ focusable: {
+ name: 'focusable',
+ description: `Makes the component a focusable element. When this element gains keyboard focus, it gets a \`data-focus-visible\` attribute and triggers the \`onFocusVisible\` prop.
+
+The component supports the \`disabled\` prop even for those elements not supporting the native \`disabled\` attribute. Disabled elements may be still accessible via keyboard by using the the \`accessibleWhenDisabled\` prop.
+
+Non-native focusable elements will lose their focusability entirely. However, native focusable elements will retain their inherent focusability.`,
+ table: {
+ type: {
+ summary: 'boolean',
+ },
+ },
+ },
+ disabled: {
+ name: 'disabled',
+ description: `Determines if the element is disabled. This sets the \`aria-disabled\` attribute accordingly, enabling support for all elements, including those that don't support the native \`disabled\` attribute.
+
+This feature can be combined with the \`accessibleWhenDisabled\` prop to
+make disabled elements still accessible via keyboard.
+
+**Note**: For this prop to work, the \`focusable\` prop must be set to
+\`true\`, if it's not set by default.`,
+ table: {
+ defaultValue: {
+ summary: 'false',
+ },
+ type: {
+ summary: 'boolean',
+ },
+ },
+ },
+ accessibleWhenDisabled,
+ onFocusVisible: {
+ name: 'onFocusVisible',
+ description: `Custom event handler invoked when the element gains focus through keyboard interaction or a key press occurs while the element is in focus. This is the programmatic equivalent of the \`data-focus-visible\` attribute.
+
+**Note**: For this prop to work, the \`focusable\` prop must be set to \`true\` if it's not set by default.`,
+ table: {
+ type: {
+ summary:
+ '(event: SyntheticEvent) => void',
+ },
+ },
+ },
},
'Composite.Group': commonArgTypes,
'Composite.GroupLabel': commonArgTypes,
'Composite.Row': commonArgTypes,
- 'Composite.Item': commonArgTypes,
+ 'Composite.Item': {
+ ...commonArgTypes,
+ accessibleWhenDisabled,
+ },
'Composite.Hover': commonArgTypes,
'Composite.Typeahead': commonArgTypes,
};
diff --git a/packages/components/src/composite/types.ts b/packages/components/src/composite/types.ts
index 8bd4b447a83aef..5afe410f7582ba 100644
--- a/packages/components/src/composite/types.ts
+++ b/packages/components/src/composite/types.ts
@@ -131,6 +131,57 @@ export type CompositeProps = {
* merged.
*/
render?: Ariakit.CompositeProps[ 'render' ];
+ /**
+ * Makes the component a focusable element. When this element gains keyboard
+ * focus, it gets a `data-focus-visible` attribute and triggers the
+ * `onFocusVisible` prop.
+ * The component supports the `disabled` prop even for those elements not
+ * supporting the native `disabled` attribute. Disabled elements may be
+ * still accessible via keyboard by using the the `accessibleWhenDisabled`
+ * prop.
+ * Non-native focusable elements will lose their focusability entirely.
+ * However, native focusable elements will retain their inherent focusability.
+ */
+ focusable?: Ariakit.CompositeProps[ 'focusable' ];
+ /**
+ * Determines if the element is disabled. This sets the `aria-disabled`
+ * attribute accordingly, enabling support for all elements, including those
+ * that don't support the native `disabled` attribute.
+ *
+ * This feature can be combined with the `accessibleWhenDisabled` prop to
+ * make disabled elements still accessible via keyboard.
+ *
+ * **Note**: For this prop to work, the `focusable` prop must be set to
+ * `true`, if it's not set by default.
+ *
+ * @default false
+ */
+ disabled?: Ariakit.CompositeProps[ 'disabled' ];
+ /**
+ * Indicates whether the element should be focusable even when it is
+ * `disabled`.
+ *
+ * This is important when discoverability is a concern. For example:
+ *
+ * > A toolbar in an editor contains a set of special smart paste functions
+ * that are disabled when the clipboard is empty or when the function is not
+ * applicable to the current content of the clipboard. It could be helpful to
+ * keep the disabled buttons focusable if the ability to discover their
+ * functionality is primarily via their presence on the toolbar.
+ *
+ * Learn more on [Focusability of disabled
+ * controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols).
+ */
+ accessibleWhenDisabled?: Ariakit.CompositeProps[ 'accessibleWhenDisabled' ];
+ /**
+ * Custom event handler invoked when the element gains focus through keyboard
+ * interaction or a key press occurs while the element is in focus. This is
+ * the programmatic equivalent of the `data-focus-visible` attribute.
+ *
+ * **Note**: For this prop to work, the `focusable` prop must be set to `true`
+ * if it's not set by default.
+ */
+ onFocusVisible?: Ariakit.CompositeProps[ 'onFocusVisible' ];
/**
* The contents of the component.
*/
@@ -177,6 +228,22 @@ export type CompositeItemProps = {
* The contents of the component.
*/
children?: Ariakit.CompositeItemProps[ 'children' ];
+ /**
+ * Indicates whether the element should be focusable even when it is
+ * `disabled`.
+ *
+ * This is important when discoverability is a concern. For example:
+ *
+ * > A toolbar in an editor contains a set of special smart paste functions
+ * that are disabled when the clipboard is empty or when the function is not
+ * applicable to the current content of the clipboard. It could be helpful to
+ * keep the disabled buttons focusable if the ability to discover their
+ * functionality is primarily via their presence on the toolbar.
+ *
+ * Learn more on [Focusability of disabled
+ * controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols).
+ */
+ accessibleWhenDisabled?: Ariakit.CompositeItemProps[ 'accessibleWhenDisabled' ];
};
export type CompositeRowProps = {
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index 4c724a461e6775..cd6d2a77db9cb6 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -62,7 +62,7 @@ export {
CompositeItem as __unstableCompositeItem,
useCompositeState as __unstableUseCompositeState,
} from './composite/legacy';
-export { Composite } from './composite';
+export { Composite, useCompositeStore } from './composite';
export { ConfirmDialog as __experimentalConfirmDialog } from './confirm-dialog';
export { default as CustomSelectControl } from './custom-select-control';
export { default as Dashicon } from './dashicon';
From ad37f2063021a9d151253ca1386c213ef300fe6d Mon Sep 17 00:00:00 2001
From: Ben Dwyer
Date: Tue, 13 Aug 2024 10:03:02 +0100
Subject: [PATCH 058/126] Zoom Out: Defalt the inserter in the patterns tab
when in zoom out (#64193)
Co-authored-by: scruffian
Co-authored-by: MaggieCabrera
---
.../block-editor/src/components/inserter/menu.js | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js
index c5f41f9e3bf0a0..629765315c1d6b 100644
--- a/packages/block-editor/src/components/inserter/menu.js
+++ b/packages/block-editor/src/components/inserter/menu.js
@@ -73,9 +73,16 @@ function InserterMenu(
const [ patternFilter, setPatternFilter ] = useState( 'all' );
const [ selectedMediaCategory, setSelectedMediaCategory ] =
useState( null );
- const [ selectedTab, setSelectedTab ] = useState(
- __experimentalInitialTab
- );
+ function getInitialTab() {
+ if ( __experimentalInitialTab ) {
+ return __experimentalInitialTab;
+ }
+
+ if ( isZoomOutMode ) {
+ return 'patterns';
+ }
+ }
+ const [ selectedTab, setSelectedTab ] = useState( getInitialTab() );
const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] =
useInsertionPoint( {
From 943b72aecdfa160e925e43dad4cfbc9f9b277810 Mon Sep 17 00:00:00 2001
From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com>
Date: Tue, 13 Aug 2024 14:39:56 +0530
Subject: [PATCH 059/126] Fix: Embed blocks: adding captions via toolbar
(#64394)
* Add the Caption component to be used for consistency on the embed block similar to audio and image
* Change the label for the embed caption text
Co-authored-by: hbhalodia
Co-authored-by: Rishit30G
Co-authored-by: t-hamano
Co-authored-by: simison
---
packages/block-library/src/embed/edit.js | 9 +++++
.../block-library/src/embed/embed-preview.js | 40 ++-----------------
2 files changed, 12 insertions(+), 37 deletions(-)
diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js
index 12265883d6963c..0b589eb68ecf49 100644
--- a/packages/block-library/src/embed/edit.js
+++ b/packages/block-library/src/embed/edit.js
@@ -14,6 +14,7 @@ import { embedContentIcon } from './icons';
import EmbedLoading from './embed-loading';
import EmbedPlaceholder from './embed-placeholder';
import EmbedPreview from './embed-preview';
+import { Caption } from '../utils/caption';
/**
* External dependencies
@@ -277,6 +278,14 @@ const EmbedEdit = ( props ) => {
label={ label }
insertBlocksAfter={ insertBlocksAfter }
/>
+
>
);
diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js
index ba079cc2e1e8b6..d53f1148cee13c 100644
--- a/packages/block-library/src/embed/embed-preview.js
+++ b/packages/block-library/src/embed/embed-preview.js
@@ -13,13 +13,8 @@ import clsx from 'clsx';
*/
import { __, sprintf } from '@wordpress/i18n';
import { Placeholder, SandBox } from '@wordpress/components';
-import {
- RichText,
- BlockIcon,
- __experimentalGetElementClassName,
-} from '@wordpress/block-editor';
+import { BlockIcon } from '@wordpress/block-editor';
import { Component } from '@wordpress/element';
-import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
import { getAuthority } from '@wordpress/url';
/**
@@ -57,19 +52,8 @@ class EmbedPreview extends Component {
}
render() {
- const {
- preview,
- previewable,
- url,
- type,
- caption,
- onCaptionChange,
- isSelected,
- className,
- icon,
- label,
- insertBlocksAfter,
- } = this.props;
+ const { preview, previewable, url, type, className, icon, label } =
+ this.props;
const { scripts } = preview;
const { interactive } = this.state;
@@ -139,24 +123,6 @@ class EmbedPreview extends Component {
) }
- { ( ! RichText.isEmpty( caption ) || isSelected ) && (
-
- insertBlocksAfter(
- createBlock( getDefaultBlockName() )
- )
- }
- />
- ) }
);
}
From 600aa54ca87ad53b8b7e81977651ba0e5fc9affc Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Tue, 13 Aug 2024 18:27:00 +0900
Subject: [PATCH 060/126] Table Block: Hide caption toolbar button on multiple
selection (#64462)
Co-authored-by: t-hamano
Co-authored-by: Mamaduka
---
packages/block-library/src/table/edit.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js
index 5510d0031ff6be..95162f4e14c00b 100644
--- a/packages/block-library/src/table/edit.js
+++ b/packages/block-library/src/table/edit.js
@@ -95,7 +95,7 @@ function TableEdit( {
attributes,
setAttributes,
insertBlocksAfter,
- isSelected,
+ isSelected: isSingleSelected,
} ) {
const { hasFixedLayout, head, foot } = attributes;
const [ initialRowCount, setInitialRowCount ] = useState( 2 );
@@ -340,10 +340,10 @@ function TableEdit( {
}
useEffect( () => {
- if ( ! isSelected ) {
+ if ( ! isSingleSelected ) {
setSelectedCell();
}
- }, [ isSelected ] );
+ }, [ isSingleSelected ] );
useEffect( () => {
if ( hasTableCreated ) {
@@ -565,9 +565,10 @@ function TableEdit( {
) }
From 992bcde7651e93d3115f6c54e8bb485acd808bc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?=
Date: Tue, 13 Aug 2024 11:57:24 +0200
Subject: [PATCH 061/126] Add plugin template registration API (#61577)
Co-authored-by: Aljullu
Co-authored-by: cbravobernal
Co-authored-by: ntsekouras
Co-authored-by: peterwilsoncc
Co-authored-by: talldan
Co-authored-by: youknowriad
Co-authored-by: mtias
Co-authored-by: gziolo
Co-authored-by: SantosGuillamot
Co-authored-by: TimothyBJacobs
Co-authored-by: ellatrix
Co-authored-by: ndiego
Co-authored-by: carolinan
Co-authored-by: nerrad
Co-authored-by: annezazu
---
backport-changelog/6.7/7125.md | 3 +
lib/compat/wordpress-6.7/block-templates.php | 41 ++
...utenberg-rest-templates-controller-6-7.php | 203 ++++++++++
.../class-wp-block-templates-registry.php | 256 +++++++++++++
lib/compat/wordpress-6.7/compat.php | 114 ++++++
lib/compat/wordpress-6.7/rest-api.php | 51 +++
lib/load.php | 4 +
.../core-data/src/entity-types/wp-template.ts | 4 +
.../plugins/block-template-registration.php | 72 ++++
.../src/utils/is-template-removable.js | 6 +-
.../src/utils/is-template-revertable.js | 3 +-
.../src/dataviews/actions/reset-post.tsx | 3 +-
.../editor/src/dataviews/actions/utils.ts | 4 +-
packages/editor/src/dataviews/types.ts | 3 +
packages/editor/src/store/private-actions.js | 2 +-
.../src/store/utils/is-template-revertable.js | 3 +-
phpunit/block-template-test.php | 37 ++
...tenberg-rest-templates-controller-test.php | 119 ++++++
...class-wp-block-templates-registry-test.php | 192 ++++++++++
.../site-editor/template-registration.spec.js | 356 ++++++++++++++++++
20 files changed, 1469 insertions(+), 7 deletions(-)
create mode 100644 backport-changelog/6.7/7125.md
create mode 100644 lib/compat/wordpress-6.7/block-templates.php
create mode 100644 lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php
create mode 100644 lib/compat/wordpress-6.7/class-wp-block-templates-registry.php
create mode 100644 lib/compat/wordpress-6.7/compat.php
create mode 100644 packages/e2e-tests/plugins/block-template-registration.php
create mode 100644 phpunit/block-template-test.php
create mode 100644 phpunit/class-gutenberg-rest-templates-controller-test.php
create mode 100644 phpunit/class-wp-block-templates-registry-test.php
create mode 100644 test/e2e/specs/site-editor/template-registration.spec.js
diff --git a/backport-changelog/6.7/7125.md b/backport-changelog/6.7/7125.md
new file mode 100644
index 00000000000000..ce208decd2d145
--- /dev/null
+++ b/backport-changelog/6.7/7125.md
@@ -0,0 +1,3 @@
+https://github.com/WordPress/wordpress-develop/pull/7125
+
+* https://github.com/WordPress/gutenberg/pull/61577
diff --git a/lib/compat/wordpress-6.7/block-templates.php b/lib/compat/wordpress-6.7/block-templates.php
new file mode 100644
index 00000000000000..e270ab226c1d9f
--- /dev/null
+++ b/lib/compat/wordpress-6.7/block-templates.php
@@ -0,0 +1,41 @@
+register( $template_name, $args );
+ }
+}
+
+if ( ! function_exists( 'wp_unregister_block_template' ) ) {
+ /**
+ * Unregister a template.
+ *
+ * @param string $template_name Template name in the form of `plugin_uri//template_name`.
+ * @return true|WP_Error True on success, WP_Error on failure or if the template doesn't exist.
+ */
+ function wp_unregister_block_template( $template_name ) {
+ return WP_Block_Templates_Registry::get_instance()->unregister( $template_name );
+ }
+}
diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php
new file mode 100644
index 00000000000000..ed67dded75ecb1
--- /dev/null
+++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php
@@ -0,0 +1,203 @@
+post_type );
+ } else {
+ $template = get_block_template( $request['id'], $this->post_type );
+ }
+
+ if ( ! $template ) {
+ return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
+ }
+
+ return $this->prepare_item_for_response( $template, $request );
+ }
+
+ /**
+ * Prepare a single template output for response
+ *
+ * @param WP_Block_Template $item Template instance.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response Response object.
+ */
+ // @core-merge: Fix wrong author in plugin templates.
+ public function prepare_item_for_response( $item, $request ) {
+ $template = $item;
+
+ $fields = $this->get_fields_for_response( $request );
+
+ if ( 'plugin' !== $item->origin ) {
+ return parent::prepare_item_for_response( $item, $request );
+ }
+ $cloned_item = clone $item;
+ // Set the origin as theme when calling the previous `prepare_item_for_response()` to prevent warnings when generating the author text.
+ $cloned_item->origin = 'theme';
+ $response = parent::prepare_item_for_response( $cloned_item, $request );
+ $data = $response->data;
+
+ if ( rest_is_field_included( 'origin', $fields ) ) {
+ $data['origin'] = 'plugin';
+ }
+
+ if ( rest_is_field_included( 'plugin', $fields ) ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $cloned_item->slug );
+ if ( $registered_template ) {
+ $data['plugin'] = $registered_template->plugin;
+ }
+ }
+
+ if ( rest_is_field_included( 'author_text', $fields ) ) {
+ $data['author_text'] = $this->get_wp_templates_author_text_field( $template );
+ }
+
+ if ( rest_is_field_included( 'original_source', $fields ) ) {
+ $data['original_source'] = $this->get_wp_templates_original_source_field( $template );
+ }
+
+ $response = rest_ensure_response( $data );
+
+ if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
+ $links = $this->prepare_links( $template->id );
+ $response->add_links( $links );
+ if ( ! empty( $links['self']['href'] ) ) {
+ $actions = $this->get_available_actions();
+ $self = $links['self']['href'];
+ foreach ( $actions as $rel ) {
+ $response->add_link( $rel, $self );
+ }
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * Returns the source from where the template originally comes from.
+ *
+ * @param WP_Block_Template $template_object Template instance.
+ * @return string Original source of the template one of theme, plugin, site, or user.
+ */
+ // @core-merge: Changed the comments format (from inline to multi-line) in the entire function.
+ private static function get_wp_templates_original_source_field( $template_object ) {
+ if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
+ /*
+ * Added by theme.
+ * Template originally provided by a theme, but customized by a user.
+ * Templates originally didn't have the 'origin' field so identify
+ * older customized templates by checking for no origin and a 'theme'
+ * or 'custom' source.
+ */
+ if ( $template_object->has_theme_file &&
+ ( 'theme' === $template_object->origin || (
+ empty( $template_object->origin ) && in_array(
+ $template_object->source,
+ array(
+ 'theme',
+ 'custom',
+ ),
+ true
+ ) )
+ )
+ ) {
+ return 'theme';
+ }
+
+ // Added by plugin.
+ // @core-merge: Removed `$template_object->has_theme_file` check from this if clause.
+ if ( 'plugin' === $template_object->origin ) {
+ return 'plugin';
+ }
+
+ /*
+ * Added by site.
+ * Template was created from scratch, but has no author. Author support
+ * was only added to templates in WordPress 5.9. Fallback to showing the
+ * site logo and title.
+ */
+ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
+ return 'site';
+ }
+ }
+
+ // Added by user.
+ return 'user';
+ }
+
+ /**
+ * Returns a human readable text for the author of the template.
+ *
+ * @param WP_Block_Template $template_object Template instance.
+ * @return string Human readable text for the author.
+ */
+ private static function get_wp_templates_author_text_field( $template_object ) {
+ $original_source = self::get_wp_templates_original_source_field( $template_object );
+ switch ( $original_source ) {
+ case 'theme':
+ $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
+ return empty( $theme_name ) ? $template_object->theme : $theme_name;
+ case 'plugin':
+ // @core-merge: Prioritize plugin name instead of theme name for plugin-registered templates.
+ if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'get_plugin_data' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+ if ( isset( $template_object->plugin ) ) {
+ $plugins = wp_get_active_and_valid_plugins();
+
+ foreach ( $plugins as $plugin_file ) {
+ $plugin_basename = plugin_basename( $plugin_file );
+ // Split basename by '/' to get the plugin slug.
+ list( $plugin_slug, ) = explode( '/', $plugin_basename );
+
+ if ( $plugin_slug === $template_object->plugin ) {
+ $plugin_data = get_plugin_data( $plugin_file );
+
+ if ( ! empty( $plugin_data['Name'] ) ) {
+ return $plugin_data['Name'];
+ }
+
+ break;
+ }
+ }
+ }
+
+ /*
+ * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
+ * compatibility with templates that were registered before the plugin attribute was added.
+ */
+ $plugins = get_plugins();
+ $plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) );
+ if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
+ return $plugins[ $plugin_basename ]['Name'];
+ }
+ return isset( $template_object->plugin ) ?
+ $template_object->plugin :
+ $template_object->theme;
+ // @core-merge: End of changes to merge in core.
+ case 'site':
+ return get_bloginfo( 'name' );
+ case 'user':
+ $author = get_user_by( 'id', $template_object->author );
+ if ( ! $author ) {
+ return __( 'Unknown author' );
+ }
+ return $author->get( 'display_name' );
+ }
+ }
+}
diff --git a/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php
new file mode 100644
index 00000000000000..db53f735e13b3d
--- /dev/null
+++ b/lib/compat/wordpress-6.7/class-wp-block-templates-registry.php
@@ -0,0 +1,256 @@
+ $instance` pairs.
+ *
+ * @since 6.7.0
+ * @var WP_Block_Template[] $registered_block_templates Registered templates.
+ */
+ private $registered_templates = array();
+
+ /**
+ * Container for the main instance of the class.
+ *
+ * @since 6.7.0
+ * @var WP_Block_Templates_Registry|null
+ */
+ private static $instance = null;
+
+ /**
+ * Registers a template.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name including namespace.
+ * @param array $args Optional. Array of template arguments.
+ * @return WP_Block_Template|WP_Error The registered template on success, or false on failure.
+ */
+ public function register( $template_name, $args = array() ) {
+
+ $template = null;
+
+ $error_message = '';
+ $error_code = '';
+ if ( ! is_string( $template_name ) ) {
+ $error_message = __( 'Template names must be strings.', 'gutenberg' );
+ $error_code = 'template_name_no_string';
+ } elseif ( preg_match( '/[A-Z]+/', $template_name ) ) {
+ $error_message = __( 'Template names must not contain uppercase characters.', 'gutenberg' );
+ $error_code = 'template_name_no_uppercase';
+ } elseif ( ! preg_match( '/^[a-z0-9-]+\/\/[a-z0-9-]+$/', $template_name ) ) {
+ $error_message = __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', 'gutenberg' );
+ $error_code = 'template_no_prefix';
+ } elseif ( $this->is_registered( $template_name ) ) {
+ /* translators: %s: Template name. */
+ $error_message = sprintf( __( 'Template "%s" is already registered.', 'gutenberg' ), $template_name );
+ $error_code = 'template_already_registered';
+ }
+
+ if ( $error_message ) {
+ _doing_it_wrong(
+ __METHOD__,
+ $error_message,
+ '6.7.0'
+ );
+ return new WP_Error( $error_code, $error_message );
+ }
+
+ if ( ! $template ) {
+ $theme_name = get_stylesheet();
+ list( $plugin, $slug ) = explode( '//', $template_name );
+ $default_template_types = get_default_block_template_types();
+
+ $template = new WP_Block_Template();
+ $template->id = $theme_name . '//' . $slug;
+ $template->theme = $theme_name;
+ $template->plugin = $plugin;
+ $template->author = null;
+ $template->content = isset( $args['content'] ) ? $args['content'] : '';
+ $template->source = 'plugin';
+ $template->slug = $slug;
+ $template->type = 'wp_template';
+ $template->title = isset( $args['title'] ) ? $args['title'] : $template_name;
+ $template->description = isset( $args['description'] ) ? $args['description'] : '';
+ $template->status = 'publish';
+ $template->origin = 'plugin';
+ $template->is_custom = ! isset( $default_template_types[ $template_name ] );
+ $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : array();
+ }
+
+ $this->registered_templates[ $template_name ] = $template;
+
+ return $template;
+ }
+
+ /**
+ * Retrieves all registered templates.
+ *
+ * @since 6.7.0
+ *
+ * @return WP_Block_Template[]|false Associative array of `$template_name => $template` pairs.
+ */
+ public function get_all_registered() {
+ return $this->registered_templates;
+ }
+
+ /**
+ * Retrieves a registered template by its name.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name including namespace.
+ * @return WP_Block_Template|null|false The registered template, or null if it is not registered.
+ */
+ public function get_registered( $template_name ) {
+ if ( ! $this->is_registered( $template_name ) ) {
+ return null;
+ }
+
+ return $this->registered_templates[ $template_name ];
+ }
+
+ /**
+ * Retrieves a registered template by its slug.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_slug Slug of the template.
+ * @return WP_Block_Template|null The registered template, or null if it is not registered.
+ */
+ public function get_by_slug( $template_slug ) {
+ $all_templates = $this->get_all_registered();
+
+ if ( ! $all_templates ) {
+ return null;
+ }
+
+ foreach ( $all_templates as $template ) {
+ if ( $template->slug === $template_slug ) {
+ return $template;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves registered templates matching a query.
+ *
+ * @since 6.7.0
+ *
+ * @param array $query {
+ * Arguments to retrieve templates. Optional, empty by default.
+ *
+ * @type string[] $slug__in List of slugs to include.
+ * @type string[] $slug__not_in List of slugs to skip.
+ * @type string $post_type Post type to get the templates for.
+ * }
+ */
+ public function get_by_query( $query = array() ) {
+ $all_templates = $this->get_all_registered();
+
+ if ( ! $all_templates ) {
+ return array();
+ }
+
+ $query = wp_parse_args(
+ $query,
+ array(
+ 'slug__in' => array(),
+ 'slug__not_in' => array(),
+ 'post_type' => '',
+ )
+ );
+ $slugs_to_include = $query['slug__in'];
+ $slugs_to_skip = $query['slug__not_in'];
+ $post_type = $query['post_type'];
+
+ $matching_templates = array();
+ foreach ( $all_templates as $template_name => $template ) {
+ if ( $slugs_to_include && ! in_array( $template->slug, $slugs_to_include, true ) ) {
+ continue;
+ }
+
+ if ( $slugs_to_skip && in_array( $template->slug, $slugs_to_skip, true ) ) {
+ continue;
+ }
+
+ if ( $post_type && ! in_array( $post_type, $template->post_types, true ) ) {
+ continue;
+ }
+
+ $matching_templates[ $template_name ] = $template;
+ }
+
+ return $matching_templates;
+ }
+
+ /**
+ * Checks if a template is registered.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name.
+ * @return bool True if the template is registered, false otherwise.
+ */
+ public function is_registered( $template_name ) {
+ return isset( $this->registered_templates[ $template_name ] );
+ }
+
+ /**
+ * Unregisters a template.
+ *
+ * @since 6.7.0
+ *
+ * @param string $template_name Template name including namespace.
+ * @return WP_Block_Template|false The unregistered template on success, or false on failure.
+ */
+ public function unregister( $template_name ) {
+ if ( ! $this->is_registered( $template_name ) ) {
+ _doing_it_wrong(
+ __METHOD__,
+ /* translators: %s: Template name. */
+ sprintf( __( 'Template "%s" is not registered.', 'gutenberg' ), $template_name ),
+ '6.7.0'
+ );
+ /* translators: %s: Template name. */
+ return new WP_Error( 'template_not_registered', __( 'Template "%s" is not registered.', 'gutenberg' ) );
+ }
+
+ $unregistered_template = $this->registered_templates[ $template_name ];
+ unset( $this->registered_templates[ $template_name ] );
+
+ return $unregistered_template;
+ }
+
+ /**
+ * Utility method to retrieve the main instance of the class.
+ *
+ * The instance will be created if it does not exist yet.
+ *
+ * @since 6.7.0
+ *
+ * @return WP_Block_Templates_Registry The main instance.
+ */
+ public static function get_instance() {
+ if ( null === self::$instance ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+ }
+}
diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php
new file mode 100644
index 00000000000000..7021cab2053eff
--- /dev/null
+++ b/lib/compat/wordpress-6.7/compat.php
@@ -0,0 +1,114 @@
+ $value ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $query_result[ $key ]->slug );
+ if ( $registered_template ) {
+ $query_result[ $key ]->plugin = $registered_template->plugin;
+ $query_result[ $key ]->origin =
+ 'theme' !== $query_result[ $key ]->origin && 'theme' !== $query_result[ $key ]->source ?
+ 'plugin' :
+ $query_result[ $key ]->origin;
+ }
+ }
+
+ if ( ! isset( $query['wp_id'] ) ) {
+ $template_files = _gutenberg_get_block_templates_files( $template_type, $query );
+
+ /*
+ * Add templates registered in the template registry. Filtering out the ones which have a theme file.
+ */
+ $registered_templates = WP_Block_Templates_Registry::get_instance()->get_by_query( $query );
+ $matching_registered_templates = array_filter(
+ $registered_templates,
+ function ( $registered_template ) use ( $template_files ) {
+ foreach ( $template_files as $template_file ) {
+ if ( $template_file['slug'] === $registered_template->slug ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ );
+ $query_result = array_merge( $query_result, $matching_registered_templates );
+ }
+
+ return $query_result;
+}
+add_filter( 'get_block_templates', '_gutenberg_add_block_templates_from_registry', 10, 3 );
+
+/**
+ * Hooks into `get_block_template` to add the `plugin` property when necessary.
+ *
+ * @param [WP_Block_Template|null] $block_template The found block template, or null if there isn’t one.
+ * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was reigstered by a plugin.
+ */
+function _gutenberg_add_block_template_plugin_attribute( $block_template ) {
+ if ( $block_template ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug );
+ if ( $registered_template ) {
+ $block_template->plugin = $registered_template->plugin;
+ $block_template->origin =
+ 'theme' !== $block_template->origin && 'theme' !== $block_template->source ?
+ 'plugin' :
+ $block_template->origin;
+ }
+ }
+
+ return $block_template;
+}
+add_filter( 'get_block_template', '_gutenberg_add_block_template_plugin_attribute', 10, 1 );
+
+/**
+ * Hooks into `get_block_file_template` so templates from the registry are also returned.
+ *
+ * @param WP_Block_Template|null $block_template The found block template, or null if there is none.
+ * @param string $id Template unique identifier (example: 'theme_slug//template_slug').
+ * @return WP_Block_Template|null The block template that was already found or from the registry. In case the template was already found, add the necessary details from the registry.
+ */
+function _gutenberg_add_block_file_templates_from_registry( $block_template, $id ) {
+ if ( $block_template ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $block_template->slug );
+ if ( $registered_template ) {
+ $block_template->plugin = $registered_template->plugin;
+ $block_template->origin =
+ 'theme' !== $block_template->origin && 'theme' !== $block_template->source ?
+ 'plugin' :
+ $block_template->origin;
+ }
+ return $block_template;
+ }
+
+ $parts = explode( '//', $id, 2 );
+
+ if ( count( $parts ) < 2 ) {
+ return $block_template;
+ }
+
+ list( , $slug ) = $parts;
+ return WP_Block_Templates_Registry::get_instance()->get_by_slug( $slug );
+}
+add_filter( 'get_block_file_template', '_gutenberg_add_block_file_templates_from_registry', 10, 2 );
diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php
index 713d31c4632c74..fe2aac9c2580ae 100644
--- a/lib/compat/wordpress-6.7/rest-api.php
+++ b/lib/compat/wordpress-6.7/rest-api.php
@@ -29,3 +29,54 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) {
return $paths;
}
add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 );
+
+if ( ! function_exists( 'wp_api_template_registry' ) ) {
+ /**
+ * Hook in to the template and template part post types and modify the rest
+ * endpoint to include modifications to read templates from the
+ * BlockTemplatesRegistry.
+ *
+ * @param array $args Current registered post type args.
+ * @param string $post_type Name of post type.
+ *
+ * @return array
+ */
+ function wp_api_template_registry( $args, $post_type ) {
+ if ( 'wp_template' === $post_type || 'wp_template_part' === $post_type ) {
+ $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_7';
+ }
+ return $args;
+ }
+}
+add_filter( 'register_post_type_args', 'wp_api_template_registry', 10, 2 );
+
+/**
+ * Adds `plugin` fields to WP_REST_Templates_Controller class.
+ */
+function gutenberg_register_wp_rest_templates_controller_plugin_field() {
+
+ register_rest_field(
+ 'wp_template',
+ 'plugin',
+ array(
+ 'get_callback' => function ( $template_object ) {
+ if ( $template_object ) {
+ $registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template_object['slug'] );
+ if ( $registered_template ) {
+ return $registered_template->plugin;
+ }
+ }
+
+ return;
+ },
+ 'update_callback' => null,
+ 'schema' => array(
+ 'type' => 'string',
+ 'description' => __( 'Plugin that registered the template.', 'gutenberg' ),
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit', 'embed' ),
+ ),
+ )
+ );
+}
+add_action( 'rest_api_init', 'gutenberg_register_wp_rest_templates_controller_plugin_field' );
diff --git a/lib/load.php b/lib/load.php
index c5f12af1654df2..b501f0abd1c978 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -41,6 +41,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.6/rest-api.php';
// WordPress 6.7 compat.
+ require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php';
require __DIR__ . '/compat/wordpress-6.7/rest-api.php';
// Plugin specific code.
@@ -101,9 +102,12 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.6/post.php';
// WordPress 6.7 compat.
+require __DIR__ . '/compat/wordpress-6.7/block-templates.php';
require __DIR__ . '/compat/wordpress-6.7/blocks.php';
require __DIR__ . '/compat/wordpress-6.7/block-bindings.php';
require __DIR__ . '/compat/wordpress-6.7/script-modules.php';
+require __DIR__ . '/compat/wordpress-6.7/class-wp-block-templates-registry.php';
+require __DIR__ . '/compat/wordpress-6.7/compat.php';
// Experimental features.
require __DIR__ . '/experimental/block-editor-settings-mobile.php';
diff --git a/packages/core-data/src/entity-types/wp-template.ts b/packages/core-data/src/entity-types/wp-template.ts
index ac6db09035f193..70d3e40c295dcf 100644
--- a/packages/core-data/src/entity-types/wp-template.ts
+++ b/packages/core-data/src/entity-types/wp-template.ts
@@ -73,6 +73,10 @@ declare module './base-entity-records' {
* Post ID.
*/
wp_id: number;
+ /**
+ * Plugin that registered the template.
+ */
+ plugin?: string;
/**
* Theme file exists.
*/
diff --git a/packages/e2e-tests/plugins/block-template-registration.php b/packages/e2e-tests/plugins/block-template-registration.php
new file mode 100644
index 00000000000000..a7c75552849658
--- /dev/null
+++ b/packages/e2e-tests/plugins/block-template-registration.php
@@ -0,0 +1,72 @@
+ 'Plugin Template',
+ 'description' => 'A template registered by a plugin.',
+ 'content' => 'This is a plugin-registered template.
',
+ 'post_types' => array( 'post' ),
+ )
+ );
+ add_action(
+ 'category_template_hierarchy',
+ function () {
+ return array( 'plugin-template' );
+ }
+ );
+
+ // Custom template overridden by the theme.
+ wp_register_block_template(
+ 'gutenberg//custom-template',
+ array(
+ 'title' => 'Custom Template (overridden by the theme)',
+ 'description' => 'A custom template registered by a plugin and overridden by a theme.',
+ 'content' => 'This is a plugin-registered template and overridden by a theme.
',
+ 'post_types' => array( 'post' ),
+ )
+ );
+
+ // Custom template used to test unregistration.
+ wp_register_block_template(
+ 'gutenberg//plugin-unregistered-template',
+ array(
+ 'title' => 'Plugin Unregistered Template',
+ 'description' => 'A plugin-registered template that is unregistered.',
+ 'content' => 'This is a plugin-registered template that is also unregistered.
',
+ )
+ );
+ wp_unregister_block_template( 'gutenberg//plugin-unregistered-template' );
+
+ // Custom template used to test overriding default WP templates.
+ wp_register_block_template(
+ 'gutenberg//page',
+ array(
+ 'title' => 'Plugin Page Template',
+ 'description' => 'A plugin-registered page template.',
+ 'content' => 'This is a plugin-registered page template.
',
+ )
+ );
+
+ // Custom template used to test overriding default WP templates which can be created by the user.
+ wp_register_block_template(
+ 'gutenberg//author-admin',
+ array(
+ 'title' => 'Plugin Author Template',
+ 'description' => 'A plugin-registered author template.',
+ 'content' => 'This is a plugin-registered author template.
',
+ )
+ );
+ }
+);
diff --git a/packages/edit-site/src/utils/is-template-removable.js b/packages/edit-site/src/utils/is-template-removable.js
index 9cb1de23daab75..f81cb74b022e73 100644
--- a/packages/edit-site/src/utils/is-template-removable.js
+++ b/packages/edit-site/src/utils/is-template-removable.js
@@ -7,7 +7,7 @@ import { TEMPLATE_ORIGINS } from './constants';
* Check if a template is removable.
*
* @param {Object} template The template entity to check.
- * @return {boolean} Whether the template is revertable.
+ * @return {boolean} Whether the template is removable.
*/
export default function isTemplateRemovable( template ) {
if ( ! template ) {
@@ -15,6 +15,8 @@ export default function isTemplateRemovable( template ) {
}
return (
- template.source === TEMPLATE_ORIGINS.custom && ! template.has_theme_file
+ template.source === TEMPLATE_ORIGINS.custom &&
+ ! Boolean( template.plugin ) &&
+ ! template.has_theme_file
);
}
diff --git a/packages/edit-site/src/utils/is-template-revertable.js b/packages/edit-site/src/utils/is-template-revertable.js
index a6274d07ebebb6..42413b06cd48ec 100644
--- a/packages/edit-site/src/utils/is-template-revertable.js
+++ b/packages/edit-site/src/utils/is-template-revertable.js
@@ -15,7 +15,8 @@ export default function isTemplateRevertable( template ) {
}
/* eslint-disable camelcase */
return (
- template?.source === TEMPLATE_ORIGINS.custom && template?.has_theme_file
+ template?.source === TEMPLATE_ORIGINS.custom &&
+ ( Boolean( template?.plugin ) || template?.has_theme_file )
);
/* eslint-enable camelcase */
}
diff --git a/packages/editor/src/dataviews/actions/reset-post.tsx b/packages/editor/src/dataviews/actions/reset-post.tsx
index 59199555ddd4db..cc4cea8f5c82c0 100644
--- a/packages/editor/src/dataviews/actions/reset-post.tsx
+++ b/packages/editor/src/dataviews/actions/reset-post.tsx
@@ -32,7 +32,8 @@ const resetPost: Action< Post > = {
return (
isTemplateOrTemplatePart( item ) &&
item?.source === TEMPLATE_ORIGINS.custom &&
- item?.has_theme_file
+ ( Boolean( item.type === 'wp_template' && item?.plugin ) ||
+ item?.has_theme_file )
);
},
icon: backup,
diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts
index 7da1f71728365b..33a2be16397f3f 100644
--- a/packages/editor/src/dataviews/actions/utils.ts
+++ b/packages/editor/src/dataviews/actions/utils.ts
@@ -57,6 +57,8 @@ export function isTemplateRemovable( template: Template | TemplatePart ) {
return (
[ template.source, template.source ].includes(
TEMPLATE_ORIGINS.custom
- ) && ! template.has_theme_file
+ ) &&
+ ! Boolean( template.type === 'wp_template' && template?.plugin ) &&
+ ! template.has_theme_file
);
}
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
index 514953d6691290..d207410ca2b6a5 100644
--- a/packages/editor/src/dataviews/types.ts
+++ b/packages/editor/src/dataviews/types.ts
@@ -34,6 +34,8 @@ export interface Template extends CommonPost {
type: 'wp_template';
is_custom: boolean;
source: string;
+ origin: string;
+ plugin?: string;
has_theme_file: boolean;
id: string;
}
@@ -41,6 +43,7 @@ export interface Template extends CommonPost {
export interface TemplatePart extends CommonPost {
type: 'wp_template_part';
source: string;
+ origin: string;
has_theme_file: boolean;
id: string;
area: string;
diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js
index 0996d6eb8b9d32..e22929011256d5 100644
--- a/packages/editor/src/store/private-actions.js
+++ b/packages/editor/src/store/private-actions.js
@@ -269,7 +269,7 @@ export const revertTemplate =
const fileTemplatePath = addQueryArgs(
`${ templateEntityConfig.baseURL }/${ template.id }`,
- { context: 'edit', source: 'theme' }
+ { context: 'edit', source: template.origin }
);
const fileTemplate = await apiFetch( { path: fileTemplatePath } );
diff --git a/packages/editor/src/store/utils/is-template-revertable.js b/packages/editor/src/store/utils/is-template-revertable.js
index a09715af875bc2..2cb674920e3e4c 100644
--- a/packages/editor/src/store/utils/is-template-revertable.js
+++ b/packages/editor/src/store/utils/is-template-revertable.js
@@ -18,6 +18,7 @@ export default function isTemplateRevertable( templateOrTemplatePart ) {
return (
templateOrTemplatePart.source === TEMPLATE_ORIGINS.custom &&
- templateOrTemplatePart.has_theme_file
+ ( Boolean( templateOrTemplatePart?.plugin ) ||
+ templateOrTemplatePart?.has_theme_file )
);
}
diff --git a/phpunit/block-template-test.php b/phpunit/block-template-test.php
new file mode 100644
index 00000000000000..6589aad90b8053
--- /dev/null
+++ b/phpunit/block-template-test.php
@@ -0,0 +1,37 @@
+assertArrayHasKey( $template_name, $templates );
+
+ wp_unregister_block_template( $template_name );
+ }
+
+ public function test_get_block_template_from_registry() {
+ $template_name = 'test-plugin//test-template';
+ $args = array(
+ 'title' => 'Test Template',
+ );
+
+ wp_register_block_template( $template_name, $args );
+
+ $template = get_block_template( 'block-theme//test-template' );
+
+ $this->assertEquals( 'Test Template', $template->title );
+
+ wp_unregister_block_template( $template_name );
+ }
+}
diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php
new file mode 100644
index 00000000000000..14735246c6fb20
--- /dev/null
+++ b/phpunit/class-gutenberg-rest-templates-controller-test.php
@@ -0,0 +1,119 @@
+user->create(
+ array(
+ 'role' => 'administrator',
+ )
+ );
+ }
+
+ public static function wpTearDownAfterClass() {
+ self::delete_user( self::$admin_id );
+ }
+
+ public function test_get_item() {
+ wp_set_current_user( self::$admin_id );
+
+ $template_name = 'test-plugin//test-template';
+ $args = array(
+ 'content' => 'Template content',
+ 'title' => 'Test Template',
+ 'description' => 'Description of test template',
+ 'post_types' => array( 'post', 'page' ),
+ );
+
+ wp_register_block_template( $template_name, $args );
+
+ $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertNotWPError( $response, "Fetching a registered template shouldn't cause an error." );
+
+ $data = $response->get_data();
+
+ $this->assertSame( 'default//test-template', $data['id'], 'Template ID mismatch.' );
+ $this->assertSame( 'default', $data['theme'], 'Template theme mismatch.' );
+ $this->assertSame( 'Template content', $data['content']['raw'], 'Template content mismatch.' );
+ $this->assertSame( 'test-template', $data['slug'], 'Template slug mismatch.' );
+ $this->assertSame( 'plugin', $data['source'], "Template source should be 'plugin'." );
+ $this->assertSame( 'plugin', $data['origin'], "Template origin should be 'plugin'." );
+ $this->assertSame( 'test-plugin', $data['author_text'], 'Template author text mismatch.' );
+ $this->assertSame( 'Description of test template', $data['description'], 'Template description mismatch.' );
+ $this->assertSame( 'Test Template', $data['title']['rendered'], 'Template title mismatch.' );
+ $this->assertSame( 'test-plugin', $data['plugin'], 'Plugin name mismatch.' );
+
+ wp_unregister_block_template( $template_name );
+
+ $request = new WP_REST_Request( 'GET', '/wp/v2/templates/test-plugin//test-template' );
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertNotWPError( $response, "Fetching an unregistered template shouldn't cause an error." );
+ $this->assertSame( 404, $response->get_status(), 'Fetching an unregistered template should return 404.' );
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_register_routes() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_get_items() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_create_item() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_update_item() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_delete_item() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_context_param() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_prepare_item() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function test_get_item_schema() {
+ // Already present in core test class: Tests_REST_WpRestTemplatesController.
+ }
+}
diff --git a/phpunit/class-wp-block-templates-registry-test.php b/phpunit/class-wp-block-templates-registry-test.php
new file mode 100644
index 00000000000000..fb8436eb6153d4
--- /dev/null
+++ b/phpunit/class-wp-block-templates-registry-test.php
@@ -0,0 +1,192 @@
+register( $template_name );
+
+ $this->assertSame( $template->slug, 'test-template' );
+
+ self::$registry->unregister( $template_name );
+ }
+
+ public function test_register_template_invalid_name() {
+ // Try to register a template with invalid name (non-string).
+ $template_name = array( 'invalid-template-name' );
+
+ $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' );
+ $result = self::$registry->register( $template_name );
+
+ $this->assertWPError( $result );
+ $this->assertSame( 'template_name_no_string', $result->get_error_code(), 'Error code mismatch.' );
+ $this->assertSame( 'Template names must be strings.', $result->get_error_message(), 'Error message mismatch.' );
+ }
+
+ public function test_register_template_invalid_name_uppercase() {
+ // Try to register a template with uppercase characters in the name.
+ $template_name = 'test-plugin//Invalid-Template-Name';
+
+ $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' );
+ $result = self::$registry->register( $template_name );
+
+ $this->assertWPError( $result );
+ $this->assertSame( 'template_name_no_uppercase', $result->get_error_code(), 'Error code mismatch.' );
+ $this->assertSame( 'Template names must not contain uppercase characters.', $result->get_error_message(), 'Error message mismatch.' );
+ }
+
+ public function test_register_template_no_prefix() {
+ // Try to register a template without a namespace.
+ $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' );
+ $result = self::$registry->register( 'template-no-plugin', array() );
+
+ $this->assertWPError( $result );
+ $this->assertSame( 'template_no_prefix', $result->get_error_code(), 'Error code mismatch.' );
+ $this->assertSame( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template', $result->get_error_message(), 'Error message mismatch.' );
+ }
+
+ public function test_register_template_already_exists() {
+ // Register the template for the first time.
+ $template_name = 'test-plugin//duplicate-template';
+ self::$registry->register( $template_name );
+
+ // Try to register the same template again.
+ $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' );
+ $result = self::$registry->register( $template_name );
+
+ $this->assertWPError( $result );
+ $this->assertSame( 'template_already_registered', $result->get_error_code(), 'Error code mismatch.' );
+ $this->assertStringContainsString( 'Template "test-plugin//duplicate-template" is already registered.', $result->get_error_message(), 'Error message mismatch.' );
+
+ self::$registry->unregister( $template_name );
+ }
+
+ public function test_get_all_registered() {
+ $template_name_1 = 'test-plugin//template-1';
+ $template_name_2 = 'test-plugin//template-2';
+ self::$registry->register( $template_name_1 );
+ self::$registry->register( $template_name_2 );
+
+ $all_templates = self::$registry->get_all_registered();
+
+ $this->assertIsArray( $all_templates, 'Registered templates should be an array.' );
+ $this->assertCount( 2, $all_templates, 'Registered templates should contain 2 items.' );
+ $this->assertArrayHasKey( 'test-plugin//template-1', $all_templates, 'Registered templates should contain "test-plugin//template-1".' );
+ $this->assertArrayHasKey( 'test-plugin//template-2', $all_templates, 'Registered templates should contain "test-plugin//template-2".' );
+
+ self::$registry->unregister( $template_name_1 );
+ self::$registry->unregister( $template_name_2 );
+ }
+
+ public function test_get_registered() {
+ $template_name = 'test-plugin//registered-template';
+ $args = array(
+ 'content' => 'Template content',
+ 'title' => 'Registered Template',
+ 'description' => 'Description of registered template',
+ 'post_types' => array( 'post', 'page' ),
+ );
+ self::$registry->register( $template_name, $args );
+
+ $registered_template = self::$registry->get_registered( $template_name );
+
+ $this->assertSame( 'default', $registered_template->theme, 'Template theme mismatch.' );
+ $this->assertSame( 'registered-template', $registered_template->slug, 'Template slug mismatch.' );
+ $this->assertSame( 'default//registered-template', $registered_template->id, 'Template ID mismatch.' );
+ $this->assertSame( 'Registered Template', $registered_template->title, 'Template title mismatch.' );
+ $this->assertSame( 'Template content', $registered_template->content, 'Template content mismatch.' );
+ $this->assertSame( 'Description of registered template', $registered_template->description, 'Template description mismatch.' );
+ $this->assertSame( 'plugin', $registered_template->source, "Template source should be 'plugin'." );
+ $this->assertSame( 'plugin', $registered_template->origin, "Template origin should be 'plugin'." );
+ $this->assertEquals( array( 'post', 'page' ), $registered_template->post_types, 'Template post types mismatch.' );
+ $this->assertSame( 'test-plugin', $registered_template->plugin, 'Plugin name mismatch.' );
+
+ self::$registry->unregister( $template_name );
+ }
+
+ public function test_get_by_slug() {
+ $slug = 'slug-template';
+ $template_name = 'test-plugin//' . $slug;
+ $args = array(
+ 'content' => 'Template content',
+ 'title' => 'Slug Template',
+ );
+ self::$registry->register( $template_name, $args );
+
+ $registered_template = self::$registry->get_by_slug( $slug );
+
+ $this->assertNotNull( $registered_template, 'Registered template should not be null.' );
+ $this->assertSame( $slug, $registered_template->slug, 'Template slug mismatch.' );
+
+ self::$registry->unregister( $template_name );
+ }
+
+ public function test_get_by_query() {
+ $template_name_1 = 'test-plugin//query-template-1';
+ $template_name_2 = 'test-plugin//query-template-2';
+ $args_1 = array(
+ 'content' => 'Template content 1',
+ 'title' => 'Query Template 1',
+ );
+ $args_2 = array(
+ 'content' => 'Template content 2',
+ 'title' => 'Query Template 2',
+ );
+ self::$registry->register( $template_name_1, $args_1 );
+ self::$registry->register( $template_name_2, $args_2 );
+
+ $query = array(
+ 'slug__in' => array( 'query-template-1' ),
+ );
+ $results = self::$registry->get_by_query( $query );
+
+ $this->assertCount( 1, $results, 'Query result should contain 1 item.' );
+ $this->assertArrayHasKey( $template_name_1, $results, 'Query result should contain "test-plugin//query-template-1".' );
+
+ self::$registry->unregister( $template_name_1 );
+ self::$registry->unregister( $template_name_2 );
+ }
+
+ public function test_is_registered() {
+ $template_name = 'test-plugin//is-registered-template';
+ $args = array(
+ 'content' => 'Template content',
+ 'title' => 'Is Registered Template',
+ );
+ self::$registry->register( $template_name, $args );
+
+ $this->assertTrue( self::$registry->is_registered( $template_name ) );
+
+ self::$registry->unregister( $template_name );
+ }
+
+ public function test_unregister() {
+ $template_name = 'test-plugin//unregister-template';
+ $args = array(
+ 'content' => 'Template content',
+ 'title' => 'Unregister Template',
+ );
+ $template = self::$registry->register( $template_name, $args );
+
+ $unregistered_template = self::$registry->unregister( $template_name );
+
+ $this->assertEquals( $template, $unregistered_template, 'Unregistered template should be the same as the registered one.' );
+ $this->assertFalse( self::$registry->is_registered( $template_name ), 'Template should not be registered after unregistering.' );
+ }
+}
diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js
new file mode 100644
index 00000000000000..132e3a8c49a902
--- /dev/null
+++ b/test/e2e/specs/site-editor/template-registration.spec.js
@@ -0,0 +1,356 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.use( {
+ blockTemplateRegistrationUtils: async ( { editor, page }, use ) => {
+ await use( new BlockTemplateRegistrationUtils( { editor, page } ) );
+ },
+} );
+
+test.describe( 'Block template registration', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'emptytheme' );
+ await requestUtils.activatePlugin(
+ 'gutenberg-test-block-template-registration'
+ );
+ } );
+ test.afterAll( async ( { requestUtils } ) => {
+ await requestUtils.deactivatePlugin(
+ 'gutenberg-test-block-template-registration'
+ );
+ } );
+ test.afterEach( async ( { requestUtils } ) => {
+ await requestUtils.deleteAllTemplates( 'wp_template' );
+ await requestUtils.deleteAllPosts();
+ } );
+
+ test( 'templates can be registered and edited', async ( {
+ admin,
+ editor,
+ page,
+ blockTemplateRegistrationUtils,
+ } ) => {
+ // Verify template is applied to the frontend.
+ await page.goto( '/?cat=1' );
+ await expect(
+ page.getByText( 'This is a plugin-registered template.' )
+ ).toBeVisible();
+
+ // Verify template is listed in the Site Editor.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate(
+ 'Plugin Template'
+ );
+ await expect( page.getByText( 'Plugin Template' ) ).toBeVisible();
+ await expect(
+ page.getByText( 'A template registered by a plugin.' )
+ ).toBeVisible();
+ await expect( page.getByText( 'AuthorGutenberg' ) ).toBeVisible();
+
+ // Verify the template contents are rendered in the editor.
+ await page.getByText( 'Plugin Template' ).click();
+ await expect(
+ editor.canvas.getByText( 'This is a plugin-registered template.' )
+ ).toBeVisible();
+
+ // Verify edits persist in the frontend.
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: { content: 'User-edited template' },
+ } );
+ await editor.saveSiteEditorEntities( {
+ isOnlyCurrentEntityDirty: true,
+ } );
+ await page.goto( '/?cat=1' );
+ await expect( page.getByText( 'User-edited template' ) ).toBeVisible();
+
+ // Verify template can be reset.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ const resetNotice = page
+ .getByLabel( 'Dismiss this notice' )
+ .getByText( `"Plugin Template" reset.` );
+ const savedButton = page.getByRole( 'button', {
+ name: 'Saved',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate(
+ 'Plugin Template'
+ );
+ const searchResults = page.getByLabel( 'Actions' );
+ await searchResults.first().click();
+ await page.getByRole( 'menuitem', { name: 'Reset' } ).click();
+ await page.getByRole( 'button', { name: 'Reset' } ).click();
+
+ await expect( resetNotice ).toBeVisible();
+ await expect( savedButton ).toBeVisible();
+ await page.goto( '/?cat=1' );
+ await expect(
+ page.getByText( 'Content edited template.' )
+ ).toBeHidden();
+ } );
+
+ test( 'registered templates are available in the Swap template screen', async ( {
+ admin,
+ editor,
+ page,
+ } ) => {
+ // Create a post.
+ await admin.visitAdminPage( '/post-new.php' );
+ await page.getByLabel( 'Close', { exact: true } ).click();
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: { content: 'User-created post.' },
+ } );
+
+ // Swap template.
+ await page.getByRole( 'button', { name: 'Post' } ).click();
+ await page.getByRole( 'button', { name: 'Template options' } ).click();
+ await page.getByRole( 'menuitem', { name: 'Swap template' } ).click();
+ await page.getByText( 'Plugin Template' ).click();
+
+ // Verify the template is applied.
+ const postId = await editor.publishPost();
+ await page.goto( `?p=${ postId }` );
+ await expect(
+ page.getByText( 'This is a plugin-registered template.' )
+ ).toBeVisible();
+ } );
+
+ test( 'themes can override registered templates', async ( {
+ admin,
+ editor,
+ page,
+ blockTemplateRegistrationUtils,
+ } ) => {
+ // Create a post.
+ await admin.visitAdminPage( '/post-new.php' );
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: { content: 'User-created post.' },
+ } );
+
+ // Swap template.
+ await page.getByRole( 'button', { name: 'Post' } ).click();
+ await page.getByRole( 'button', { name: 'Template options' } ).click();
+ await page.getByRole( 'menuitem', { name: 'Swap template' } ).click();
+ await page.getByText( 'Custom', { exact: true } ).click();
+
+ // Verify the theme template is applied.
+ const postId = await editor.publishPost();
+ await page.goto( `?p=${ postId }` );
+ await expect(
+ page.getByText( 'Custom template for Posts' )
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ 'This is a plugin-registered template and overridden by a theme.'
+ )
+ ).toBeHidden();
+
+ // Verify the plugin-registered template doesn't appear in the Site Editor.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate( 'Custom' );
+ await expect(
+ page.getByText( 'Custom Template (overridden by the theme)' )
+ ).toBeHidden();
+ // Verify the theme template shows the theme name as the author.
+ await expect( page.getByText( 'AuthorEmptytheme' ) ).toBeVisible();
+ } );
+
+ test( 'templates can be deleted if the registered plugin is deactivated', async ( {
+ admin,
+ editor,
+ page,
+ requestUtils,
+ blockTemplateRegistrationUtils,
+ } ) => {
+ // Make an edit to the template.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate(
+ 'Plugin Template'
+ );
+ await page.getByText( 'Plugin Template' ).click();
+ await expect(
+ editor.canvas.getByText( 'This is a plugin-registered template.' )
+ ).toBeVisible();
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: { content: 'User-customized template' },
+ } );
+ await editor.saveSiteEditorEntities( {
+ isOnlyCurrentEntityDirty: true,
+ } );
+
+ // Deactivate plugin.
+ await requestUtils.deactivatePlugin(
+ 'gutenberg-test-block-template-registration'
+ );
+
+ // Verify template can be deleted.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ const deletedNotice = page
+ .getByLabel( 'Dismiss this notice' )
+ .getByText( `"Plugin Template" deleted.` );
+ const savedButton = page.getByRole( 'button', {
+ name: 'Saved',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate(
+ 'Plugin Template'
+ );
+ const searchResults = page.getByLabel( 'Actions' );
+ await searchResults.first().click();
+ await page.getByRole( 'menuitem', { name: 'Delete' } ).click();
+ await page.getByRole( 'button', { name: 'Delete' } ).click();
+
+ await expect( deletedNotice ).toBeVisible();
+ await expect( savedButton ).toBeVisible();
+
+ // Expect template to no longer appear in the Site Editor.
+ await expect( page.getByLabel( 'Actions' ) ).toBeHidden();
+
+ // Reactivate plugin.
+ await requestUtils.activatePlugin(
+ 'gutenberg-test-block-template-registration'
+ );
+ } );
+
+ test( 'registered templates can be unregistered', async ( {
+ admin,
+ page,
+ blockTemplateRegistrationUtils,
+ } ) => {
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate(
+ 'Plugin Unregistered Template'
+ );
+ await expect(
+ page.getByText( 'Plugin Unregistered Template' )
+ ).toBeHidden();
+ } );
+
+ test( 'WP default templates can be overridden by plugins', async ( {
+ page,
+ } ) => {
+ await page.goto( '?page_id=2' );
+ await expect(
+ page.getByText( 'This is a plugin-registered page template.' )
+ ).toBeVisible();
+ } );
+
+ test( 'user-customized templates cannot be overridden by plugins', async ( {
+ admin,
+ editor,
+ page,
+ requestUtils,
+ blockTemplateRegistrationUtils,
+ } ) => {
+ await requestUtils.deactivatePlugin(
+ 'gutenberg-test-block-template-registration'
+ );
+
+ // Create an author template.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ await page.getByLabel( 'Add New Template' ).click();
+ await page.getByRole( 'button', { name: 'Author Archives' } ).click();
+ await page
+ .getByRole( 'button', { name: 'Author For a specific item' } )
+ .click();
+ await page.getByRole( 'option', { name: 'admin' } ).click();
+ await expect( page.getByText( 'Choose a pattern' ) ).toBeVisible();
+ await page.getByLabel( 'Close', { exact: true } ).click();
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: { content: 'Author template customized by the user.' },
+ } );
+ await editor.saveSiteEditorEntities( {
+ isOnlyCurrentEntityDirty: true,
+ } );
+
+ await requestUtils.activatePlugin(
+ 'gutenberg-test-block-template-registration'
+ );
+
+ // Verify the template edited by the user has priority over the one registered by the theme.
+ await page.goto( '?author=1' );
+ await expect(
+ page.getByText( 'Author template customized by the user.' )
+ ).toBeVisible();
+ await expect(
+ page.getByText( 'This is a plugin-registered author template.' )
+ ).toBeHidden();
+
+ // Verify the template registered by the plugin is not visible in the Site Editor.
+ await admin.visitSiteEditor( {
+ postType: 'wp_template',
+ } );
+ await blockTemplateRegistrationUtils.searchForTemplate(
+ 'Plugin Author Template'
+ );
+ await expect( page.getByText( 'Plugin Author Template' ) ).toBeHidden();
+
+ // Reset the user-modified template.
+ const resetNotice = page
+ .getByLabel( 'Dismiss this notice' )
+ .getByText( `"Author: Admin" reset.` );
+ await page.getByPlaceholder( 'Search' ).fill( 'Author: admin' );
+ await page.getByRole( 'link', { name: 'Author: Admin' } ).click();
+ const actions = page.getByLabel( 'Actions' );
+ await actions.first().click();
+ await page.getByRole( 'menuitem', { name: 'Reset' } ).click();
+ await page.getByRole( 'button', { name: 'Reset' } ).click();
+
+ await expect( resetNotice ).toBeVisible();
+
+ // Verify the template registered by the plugin is applied in the editor...
+ await expect(
+ editor.canvas.getByText( 'Author template customized by the user.' )
+ ).toBeHidden();
+ await expect(
+ editor.canvas.getByText(
+ 'This is a plugin-registered author template.'
+ )
+ ).toBeVisible();
+
+ // ... and the frontend.
+ await page.goto( '?author=1' );
+ await expect(
+ page.getByText( 'Author template customized by the user.' )
+ ).toBeHidden();
+ await expect(
+ page.getByText( 'This is a plugin-registered author template.' )
+ ).toBeVisible();
+ } );
+} );
+
+class BlockTemplateRegistrationUtils {
+ constructor( { page } ) {
+ this.page = page;
+ }
+
+ async searchForTemplate( searchTerm ) {
+ const searchResults = this.page.getByLabel( 'Actions' );
+ await expect
+ .poll( async () => await searchResults.count() )
+ .toBeGreaterThan( 0 );
+ const initialSearchResultsCount = await searchResults.count();
+ await this.page.getByPlaceholder( 'Search' ).fill( searchTerm );
+ await expect
+ .poll( async () => await searchResults.count() )
+ .toBeLessThan( initialSearchResultsCount );
+ }
+}
From 7502c6a39ccb50df5fc1be7c11bac4d8e04aa48b Mon Sep 17 00:00:00 2001
From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com>
Date: Tue, 13 Aug 2024 12:13:46 +0200
Subject: [PATCH 062/126] Fit items into 100% width (#64465)
Fix long keys overflow in bindings panel
Co-authored-by: SantosGuillamot
Co-authored-by: jasmussen
---
packages/block-editor/src/hooks/block-bindings.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/block-editor/src/hooks/block-bindings.scss b/packages/block-editor/src/hooks/block-bindings.scss
index 73e7c490160d3e..603b2115623b8f 100644
--- a/packages/block-editor/src/hooks/block-bindings.scss
+++ b/packages/block-editor/src/hooks/block-bindings.scss
@@ -1,5 +1,5 @@
div.block-editor-bindings__panel {
- grid-template-columns: auto;
+ grid-template-columns: repeat(auto-fit, minmax(100%, 1fr));
button:hover .block-editor-bindings__item-explanation {
color: inherit;
}
From ea0bcf1ce1345afc840595040a6f4bf9be5faf9c Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Tue, 13 Aug 2024 12:35:17 +0200
Subject: [PATCH 063/126] DataViews Extensibility: Allow unregistering the view
post revisions action (#64464)
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
---
packages/dataviews/src/types.ts | 6 +++
.../src/components/post-actions/actions.js | 40 +---------------
.../dataviews/actions/view-post-revisions.tsx | 47 +++++++++++++++++++
.../src/dataviews/store/private-actions.ts | 4 ++
packages/editor/src/dataviews/types.ts | 9 +++-
5 files changed, 66 insertions(+), 40 deletions(-)
create mode 100644 packages/editor/src/dataviews/actions/view-post-revisions.tsx
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 7bbbc8cb863c09..fa5cec8d7d0320 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -426,6 +426,12 @@ interface ActionBase< Item > {
* Whether the action can be used as a bulk action.
*/
supportsBulk?: boolean;
+
+ /**
+ * The context in which the action is visible.
+ * This is only a "meta" information for now.
+ */
+ context?: 'list' | 'single';
}
export interface RenderModalProps< Item > {
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 627de5e8652e47..831a4f5349869b 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -2,10 +2,9 @@
* WordPress dependencies
*/
import { external } from '@wordpress/icons';
-import { addQueryArgs } from '@wordpress/url';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
-import { __, sprintf } from '@wordpress/i18n';
+import { __ } from '@wordpress/i18n';
import { useMemo, useEffect } from '@wordpress/element';
/**
@@ -31,40 +30,6 @@ const viewPostAction = {
},
};
-const postRevisionsAction = {
- id: 'view-post-revisions',
- context: 'list',
- label( items ) {
- const revisionsCount =
- items[ 0 ]._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
- return sprintf(
- /* translators: %s: number of revisions */
- __( 'View revisions (%s)' ),
- revisionsCount
- );
- },
- isEligible: ( post ) => {
- if ( post.status === 'trash' ) {
- return false;
- }
- const lastRevisionId =
- post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id ?? null;
- const revisionsCount =
- post?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
- return lastRevisionId && revisionsCount > 1;
- },
- callback( posts, { onActionPerformed } ) {
- const post = posts[ 0 ];
- const href = addQueryArgs( 'revision.php', {
- revision: post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id,
- } );
- document.location.href = href;
- if ( onActionPerformed ) {
- onActionPerformed( posts );
- }
- },
-};
-
export function usePostActions( { postType, onActionPerformed, context } ) {
const { defaultActions, postTypeObject } = useSelect(
( select ) => {
@@ -84,7 +49,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
}, [ registerPostTypeActions, postType ] );
const isLoaded = !! postTypeObject;
- const supportsRevisions = !! postTypeObject?.supports?.revisions;
return useMemo( () => {
if ( ! isLoaded ) {
return [];
@@ -92,7 +56,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
let actions = [
postTypeObject?.viewable && viewPostAction,
- supportsRevisions && postRevisionsAction,
...defaultActions,
].filter( Boolean );
// Filter actions based on provided context. If not provided
@@ -161,7 +124,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
postTypeObject?.viewable,
onActionPerformed,
isLoaded,
- supportsRevisions,
context,
] );
}
diff --git a/packages/editor/src/dataviews/actions/view-post-revisions.tsx b/packages/editor/src/dataviews/actions/view-post-revisions.tsx
new file mode 100644
index 00000000000000..875b925b94f070
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/view-post-revisions.tsx
@@ -0,0 +1,47 @@
+/**
+ * WordPress dependencies
+ */
+import { addQueryArgs } from '@wordpress/url';
+import { __, sprintf } from '@wordpress/i18n';
+import type { Action } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+import type { Post } from '../types';
+
+const viewPostRevisions: Action< Post > = {
+ id: 'view-post-revisions',
+ context: 'list',
+ label( items ) {
+ const revisionsCount =
+ items[ 0 ]._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
+ return sprintf(
+ /* translators: %s: number of revisions */
+ __( 'View revisions (%s)' ),
+ revisionsCount
+ );
+ },
+ isEligible( post ) {
+ if ( post.status === 'trash' ) {
+ return false;
+ }
+ const lastRevisionId =
+ post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id ?? null;
+ const revisionsCount =
+ post?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
+ return !! lastRevisionId && revisionsCount > 1;
+ },
+ callback( posts, { onActionPerformed } ) {
+ const post = posts[ 0 ];
+ const href = addQueryArgs( 'revision.php', {
+ revision: post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id,
+ } );
+ document.location.href = href;
+ if ( onActionPerformed ) {
+ onActionPerformed( posts );
+ }
+ },
+};
+
+export default viewPostRevisions;
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index d5b299b012e364..3ac121aea7393b 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -22,6 +22,7 @@ import type { PostType } from '../types';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import duplicatePost from '../actions/duplicate-post';
+import viewPostRevisions from '../actions/view-post-revisions';
export function registerEntityAction< Item >(
kind: string,
@@ -88,6 +89,9 @@ export const registerPostTypeActions =
.getCurrentTheme();
const actions = [
+ !! postTypeConfig?.supports?.revisions
+ ? viewPostRevisions
+ : undefined,
// @ts-ignore
globalThis.IS_GUTENBERG_PLUGIN
? ! [ 'wp_template', 'wp_block', 'wp_template_part' ].includes(
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
index d207410ca2b6a5..e5886792fb9c33 100644
--- a/packages/editor/src/dataviews/types.ts
+++ b/packages/editor/src/dataviews/types.ts
@@ -14,6 +14,13 @@ export interface CommonPost {
type: string;
id: string | number;
blocks?: Object[];
+ _links?: Links;
+}
+
+interface Links {
+ 'predecessor-version'?: { href: string; id: number }[];
+ 'version-history'?: { href: string; count: number }[];
+ [ key: string ]: { href: string }[] | undefined;
}
export interface BasePost extends CommonPost {
@@ -27,7 +34,6 @@ export interface BasePost extends CommonPost {
featured_media?: number;
menu_order?: number;
ping_status?: 'open' | 'closed';
- _links?: Record< string, { href: string }[] >;
}
export interface Template extends CommonPost {
@@ -69,6 +75,7 @@ export interface PostType {
supports?: {
'page-attributes'?: boolean;
title?: boolean;
+ revisions?: boolean;
};
}
From 64643ed8727d915557c4bef65f247ab287d72465 Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Tue, 13 Aug 2024 14:59:14 +0400
Subject: [PATCH 064/126] Block Editor: Refactor inner blocks appender
components (#64470)
* Block Editor: Refactor inner blocks appender components
* Remove 'withClientId' HoC
Co-authored-by: Mamaduka
Co-authored-by: youknowriad
Co-authored-by: tyxla
---
.../inner-blocks/button-block-appender.js | 12 ++++-----
.../inner-blocks/default-block-appender.js | 27 +++----------------
.../components/inner-blocks/with-client-id.js | 19 -------------
3 files changed, 9 insertions(+), 49 deletions(-)
delete mode 100644 packages/block-editor/src/components/inner-blocks/with-client-id.js
diff --git a/packages/block-editor/src/components/inner-blocks/button-block-appender.js b/packages/block-editor/src/components/inner-blocks/button-block-appender.js
index 500e59863db429..5bc788d58582f6 100644
--- a/packages/block-editor/src/components/inner-blocks/button-block-appender.js
+++ b/packages/block-editor/src/components/inner-blocks/button-block-appender.js
@@ -7,15 +7,15 @@ import clsx from 'clsx';
* Internal dependencies
*/
import BaseButtonBlockAppender from '../button-block-appender';
-import withClientId from './with-client-id';
+import { useBlockEditContext } from '../block-edit/context';
-export const ButtonBlockAppender = ( {
- clientId,
+export default function ButtonBlockAppender( {
showSeparator,
isFloating,
onAddBlock,
isToggle,
-} ) => {
+} ) {
+ const { clientId } = useBlockEditContext();
return (
);
-};
-
-export default withClientId( ButtonBlockAppender );
+}
diff --git a/packages/block-editor/src/components/inner-blocks/default-block-appender.js b/packages/block-editor/src/components/inner-blocks/default-block-appender.js
index d2e137004d83bf..91e48a2854b513 100644
--- a/packages/block-editor/src/components/inner-blocks/default-block-appender.js
+++ b/packages/block-editor/src/components/inner-blocks/default-block-appender.js
@@ -1,29 +1,10 @@
-/**
- * WordPress dependencies
- */
-import { compose } from '@wordpress/compose';
-import { withSelect } from '@wordpress/data';
-
/**
* Internal dependencies
*/
import BaseDefaultBlockAppender from '../default-block-appender';
-import withClientId from './with-client-id';
-import { store as blockEditorStore } from '../../store';
+import { useBlockEditContext } from '../block-edit/context';
-export const DefaultBlockAppender = ( { clientId } ) => {
+export default function DefaultBlockAppender() {
+ const { clientId } = useBlockEditContext();
return ;
-};
-
-export default compose( [
- withClientId,
- withSelect( ( select, { clientId } ) => {
- const { getBlockOrder } = select( blockEditorStore );
-
- const blockClientIds = getBlockOrder( clientId );
-
- return {
- lastBlockClientId: blockClientIds[ blockClientIds.length - 1 ],
- };
- } ),
-] )( DefaultBlockAppender );
+}
diff --git a/packages/block-editor/src/components/inner-blocks/with-client-id.js b/packages/block-editor/src/components/inner-blocks/with-client-id.js
deleted file mode 100644
index 97c73ae2803934..00000000000000
--- a/packages/block-editor/src/components/inner-blocks/with-client-id.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { createHigherOrderComponent } from '@wordpress/compose';
-
-/**
- * Internal dependencies
- */
-import { useBlockEditContext } from '../block-edit/context';
-
-const withClientId = createHigherOrderComponent(
- ( WrappedComponent ) => ( props ) => {
- const { clientId } = useBlockEditContext();
- return ;
- },
- 'withClientId'
-);
-
-export default withClientId;
From e8b45dacc23052952e01b4869027c9354818d74a Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Tue, 13 Aug 2024 13:14:17 +0200
Subject: [PATCH 065/126] DataViews Extensibility: Allow unregistering the view
post action (#64467)
Co-authored-by: youknowriad
Co-authored-by: Mamaduka
---
.../src/components/post-actions/actions.js | 43 ++-----------------
.../src/dataviews/actions/view-post.tsx | 30 +++++++++++++
.../src/dataviews/store/private-actions.ts | 11 +++--
packages/editor/src/dataviews/types.ts | 2 +
4 files changed, 42 insertions(+), 44 deletions(-)
create mode 100644 packages/editor/src/dataviews/actions/view-post.tsx
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 831a4f5349869b..e1c0ed1558193d 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -1,10 +1,7 @@
/**
* WordPress dependencies
*/
-import { external } from '@wordpress/icons';
import { useDispatch, useSelect } from '@wordpress/data';
-import { store as coreStore } from '@wordpress/core-data';
-import { __ } from '@wordpress/i18n';
import { useMemo, useEffect } from '@wordpress/element';
/**
@@ -13,30 +10,11 @@ import { useMemo, useEffect } from '@wordpress/element';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
-const viewPostAction = {
- id: 'view-post',
- label: __( 'View' ),
- isPrimary: true,
- icon: external,
- isEligible( post ) {
- return post.status !== 'trash';
- },
- callback( posts, { onActionPerformed } ) {
- const post = posts[ 0 ];
- window.open( post.link, '_blank' );
- if ( onActionPerformed ) {
- onActionPerformed( posts );
- }
- },
-};
-
export function usePostActions( { postType, onActionPerformed, context } ) {
- const { defaultActions, postTypeObject } = useSelect(
+ const { defaultActions } = useSelect(
( select ) => {
- const { getPostType } = select( coreStore );
const { getEntityActions } = unlock( select( editorStore ) );
return {
- postTypeObject: getPostType( postType ),
defaultActions: getEntityActions( 'postType', postType ),
};
},
@@ -48,23 +26,14 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
registerPostTypeActions( postType );
}, [ registerPostTypeActions, postType ] );
- const isLoaded = !! postTypeObject;
return useMemo( () => {
- if ( ! isLoaded ) {
- return [];
- }
-
- let actions = [
- postTypeObject?.viewable && viewPostAction,
- ...defaultActions,
- ].filter( Boolean );
// Filter actions based on provided context. If not provided
// all actions are returned. We'll have a single entry for getting the actions
// and the consumer should provide the context to filter the actions, if needed.
// Actions should also provide the `context` they support, if it's specific, to
// compare with the provided context to get all the actions.
// Right now the only supported context is `list`.
- actions = actions.filter( ( action ) => {
+ const actions = defaultActions.filter( ( action ) => {
if ( ! action.context ) {
return true;
}
@@ -119,11 +88,5 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
}
return actions;
- }, [
- defaultActions,
- postTypeObject?.viewable,
- onActionPerformed,
- isLoaded,
- context,
- ] );
+ }, [ defaultActions, onActionPerformed, context ] );
}
diff --git a/packages/editor/src/dataviews/actions/view-post.tsx b/packages/editor/src/dataviews/actions/view-post.tsx
new file mode 100644
index 00000000000000..47eb1a66d019ad
--- /dev/null
+++ b/packages/editor/src/dataviews/actions/view-post.tsx
@@ -0,0 +1,30 @@
+/**
+ * WordPress dependencies
+ */
+import { external } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import type { Action } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+import type { BasePost } from '../types';
+
+const viewPost: Action< BasePost > = {
+ id: 'view-post',
+ label: __( 'View' ),
+ isPrimary: true,
+ icon: external,
+ isEligible( post ) {
+ return post.status !== 'trash';
+ },
+ callback( posts, { onActionPerformed } ) {
+ const post = posts[ 0 ];
+ window.open( post?.link, '_blank' );
+ if ( onActionPerformed ) {
+ onActionPerformed( posts );
+ }
+ },
+};
+
+export default viewPost;
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index 3ac121aea7393b..a9101e57dd08b5 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -23,6 +23,7 @@ import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import duplicatePost from '../actions/duplicate-post';
import viewPostRevisions from '../actions/view-post-revisions';
+import viewPost from '../actions/view-post';
export function registerEntityAction< Item >(
kind: string,
@@ -89,6 +90,7 @@ export const registerPostTypeActions =
.getCurrentTheme();
const actions = [
+ postTypeConfig.viewable ? viewPost : undefined,
!! postTypeConfig?.supports?.revisions
? viewPostRevisions
: undefined,
@@ -101,9 +103,10 @@ export const registerPostTypeActions =
duplicatePost
: undefined,
postTypeConfig.slug === 'wp_template_part' &&
- canCreate &&
- currentTheme?.is_block_theme &&
- duplicateTemplatePart,
+ canCreate &&
+ currentTheme?.is_block_theme
+ ? duplicateTemplatePart
+ : undefined,
canCreate && postTypeConfig.slug === 'wp_block'
? duplicatePattern
: undefined,
@@ -121,7 +124,7 @@ export const registerPostTypeActions =
registry.batch( () => {
actions.forEach( ( action ) => {
- if ( action === undefined ) {
+ if ( ! action ) {
return;
}
unlock( registry.dispatch( editorStore ) ).registerEntityAction(
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
index e5886792fb9c33..664c2dd417201c 100644
--- a/packages/editor/src/dataviews/types.ts
+++ b/packages/editor/src/dataviews/types.ts
@@ -34,6 +34,7 @@ export interface BasePost extends CommonPost {
featured_media?: number;
menu_order?: number;
ping_status?: 'open' | 'closed';
+ link?: string;
}
export interface Template extends CommonPost {
@@ -72,6 +73,7 @@ export type PostWithPermissions = Post & {
export interface PostType {
slug: string;
+ viewable: boolean;
supports?: {
'page-attributes'?: boolean;
title?: boolean;
From 3ec1ced865757f6568c42c126acb3478b25fe2be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com>
Date: Tue, 13 Aug 2024 13:43:15 +0200
Subject: [PATCH 066/126] Add `status` to quick edit (#64398)
Co-authored-by: oandregal
Co-authored-by: youknowriad
Co-authored-by: ntsekouras
---
.../src/components/post-edit/index.js | 29 +++++++++++++-
.../src/components/post-fields/index.js | 40 +++++++++++++++----
2 files changed, 59 insertions(+), 10 deletions(-)
diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index 80304f16503705..0ec63589d97673 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -42,13 +42,38 @@ function PostEditForm( { postType, postId } ) {
);
const [ multiEdits, setMultiEdits ] = useState( {} );
const { editEntityRecord } = useDispatch( coreDataStore );
- const { fields } = usePostFields();
+ const { fields: _fields } = usePostFields();
+ const fields = useMemo(
+ () =>
+ _fields?.map( ( field ) => {
+ if ( field.id === 'status' ) {
+ return {
+ ...field,
+ elements: field.elements.filter(
+ ( element ) => element.value !== 'trash'
+ ),
+ };
+ }
+ return field;
+ } ),
+ [ _fields ]
+ );
const form = {
type: 'panel',
- fields: [ 'title', 'author', 'date', 'comment_status' ],
+ fields: [ 'title', 'status', 'date', 'author', 'comment_status' ],
};
const onChange = ( edits ) => {
for ( const id of ids ) {
+ if (
+ edits.status !== 'future' &&
+ record.status === 'future' &&
+ new Date( record.date ) > new Date()
+ ) {
+ edits.date = null;
+ }
+ if ( edits.status === 'private' && record.password ) {
+ edits.password = '';
+ }
editEntityRecord( 'postType', postType, id, edits );
if ( ids.length > 1 ) {
setMultiEdits( ( prev ) => ( {
diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js
index b03b2c6f5be3c4..9e59b23d61922d 100644
--- a/packages/edit-site/src/components/post-fields/index.js
+++ b/packages/edit-site/src/components/post-fields/index.js
@@ -42,11 +42,36 @@ import Media from '../media';
// See https://github.com/WordPress/gutenberg/issues/55886
// We do not support custom statutes at the moment.
const STATUSES = [
- { value: 'draft', label: __( 'Draft' ), icon: drafts },
- { value: 'future', label: __( 'Scheduled' ), icon: scheduled },
- { value: 'pending', label: __( 'Pending Review' ), icon: pending },
- { value: 'private', label: __( 'Private' ), icon: notAllowed },
- { value: 'publish', label: __( 'Published' ), icon: published },
+ {
+ value: 'draft',
+ label: __( 'Draft' ),
+ icon: drafts,
+ description: __( 'Not ready to publish.' ),
+ },
+ {
+ value: 'future',
+ label: __( 'Scheduled' ),
+ icon: scheduled,
+ description: __( 'Publish automatically on a chosen date.' ),
+ },
+ {
+ value: 'pending',
+ label: __( 'Pending Review' ),
+ icon: pending,
+ description: __( 'Waiting for review before publishing.' ),
+ },
+ {
+ value: 'private',
+ label: __( 'Private' ),
+ icon: notAllowed,
+ description: __( 'Only visible to site admins and editors.' ),
+ },
+ {
+ value: 'publish',
+ label: __( 'Published' ),
+ icon: published,
+ description: __( 'Visible to everyone.' ),
+ },
{ value: 'trash', label: __( 'Trash' ), icon: trash },
];
@@ -258,11 +283,10 @@ function usePostFields( viewType ) {
{
label: __( 'Status' ),
id: 'status',
- getValue: ( { item } ) =>
- STATUSES.find( ( { value } ) => value === item.status )
- ?.label ?? item.status,
+ type: 'text',
elements: STATUSES,
render: PostStatusField,
+ Edit: 'radio',
enableSorting: false,
filterBy: {
operators: [ OPERATOR_IS_ANY ],
From 9ca5628ed1cd11975086b25cd0cc318352b94619 Mon Sep 17 00:00:00 2001
From: JuanMa
Date: Tue, 13 Aug 2024 13:33:04 +0100
Subject: [PATCH 067/126] Fix tip link in DataViews docs (#64469)
Co-authored-by: juanmaguitar
Co-authored-by: oandregal
---
packages/dataviews/README.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md
index 56cecedaef66ca..368880b69b14f0 100644
--- a/packages/dataviews/README.md
+++ b/packages/dataviews/README.md
@@ -30,8 +30,7 @@ const Example = () => {
};
```
-> [!TIP]
-> At https://wordpress.github.io/gutenberg/?path=/docs/dataviews-dataviews--docs there's an example implementation of the Dataviews component."
+
## Properties
From 02d052033868ea554bbdfee9b18b766e77128a6b Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Tue, 13 Aug 2024 17:18:12 +0400
Subject: [PATCH 068/126] Edit Post: Remove user pattern preloading (#64459)
* Edit Post: Remove user pattern preloading
* Update backport changelog
Co-authored-by: Mamaduka
Co-authored-by: tyxla
---
backport-changelog/6.7/7179.md | 1 +
lib/compat/wordpress-6.7/rest-api.php | 18 ++++++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/backport-changelog/6.7/7179.md b/backport-changelog/6.7/7179.md
index f359b6610a94e6..a6e9cc4efe2cd0 100644
--- a/backport-changelog/6.7/7179.md
+++ b/backport-changelog/6.7/7179.md
@@ -1,3 +1,4 @@
https://github.com/WordPress/wordpress-develop/pull/7179
* https://github.com/WordPress/gutenberg/pull/64401
+* https://github.com/WordPress/gutenberg/pull/64459
diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php
index fe2aac9c2580ae..2520a06fbb18be 100644
--- a/lib/compat/wordpress-6.7/rest-api.php
+++ b/lib/compat/wordpress-6.7/rest-api.php
@@ -26,6 +26,24 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) {
}
}
+ if ( 'core/edit-post' === $context->name ) {
+ $reusable_blocks_key = array_search(
+ add_query_arg(
+ array(
+ 'context' => 'edit',
+ 'per_page' => -1,
+ ),
+ rest_get_route_for_post_type_items( 'wp_block' )
+ ),
+ $paths,
+ true
+ );
+
+ if ( false !== $parts_key ) {
+ unset( $paths[ $reusable_blocks_key ] );
+ }
+ }
+
return $paths;
}
add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_7', 10, 2 );
From cedbe111a2cc2b806356a985df1f47034b7545df Mon Sep 17 00:00:00 2001
From: Nik Tsekouras
Date: Tue, 13 Aug 2024 17:55:15 +0300
Subject: [PATCH 069/126] Update the copy of quick edit tooltip (#64475)
Co-authored-by: ntsekouras
Co-authored-by: youknowriad
Co-authored-by: jasmussen
---
packages/edit-site/src/components/post-list/index.js | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js
index 35ecaaa8424fd4..bbfece24518495 100644
--- a/packages/edit-site/src/components/post-list/index.js
+++ b/packages/edit-site/src/components/post-list/index.js
@@ -366,11 +366,7 @@ export default function PostList( { postType } ) {
size="compact"
isPressed={ quickEdit }
icon={ drawerRight }
- label={
- ! quickEdit
- ? __( 'Show quick edit sidebar' )
- : __( 'Close quick edit sidebar' )
- }
+ label={ __( 'Toggle details panel' ) }
onClick={ () => {
history.push( {
...location.params,
From b3ce94ea25990e329ce4c08f3d49eb07e7ec929b Mon Sep 17 00:00:00 2001
From: Marin Atanasov <8436925+tyxla@users.noreply.github.com>
Date: Tue, 13 Aug 2024 18:40:08 +0300
Subject: [PATCH 070/126] Edit Post: Fix user pattern preloading filter
(#64477)
* Edit Post: Fix user pattern preloading filter
* Backport changelog
Co-authored-by: tyxla
Co-authored-by: Mamaduka
---
backport-changelog/6.7/7179.md | 1 +
lib/compat/wordpress-6.7/rest-api.php | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/backport-changelog/6.7/7179.md b/backport-changelog/6.7/7179.md
index a6e9cc4efe2cd0..d777eace2cb05e 100644
--- a/backport-changelog/6.7/7179.md
+++ b/backport-changelog/6.7/7179.md
@@ -2,3 +2,4 @@ https://github.com/WordPress/wordpress-develop/pull/7179
* https://github.com/WordPress/gutenberg/pull/64401
* https://github.com/WordPress/gutenberg/pull/64459
+* https://github.com/WordPress/gutenberg/pull/64477
diff --git a/lib/compat/wordpress-6.7/rest-api.php b/lib/compat/wordpress-6.7/rest-api.php
index 2520a06fbb18be..081c22c8102914 100644
--- a/lib/compat/wordpress-6.7/rest-api.php
+++ b/lib/compat/wordpress-6.7/rest-api.php
@@ -39,7 +39,7 @@ function gutenberg_block_editor_preload_paths_6_7( $paths, $context ) {
true
);
- if ( false !== $parts_key ) {
+ if ( false !== $reusable_blocks_key ) {
unset( $paths[ $reusable_blocks_key ] );
}
}
From 5dae25b15e82a59d28c6c065b5a3745f3f83980f Mon Sep 17 00:00:00 2001
From: Jan Pfeil
Date: Tue, 13 Aug 2024 18:40:35 +0200
Subject: [PATCH 071/126] fix typo in block-wrapper.md (#64447)
---
docs/getting-started/fundamentals/block-wrapper.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/getting-started/fundamentals/block-wrapper.md b/docs/getting-started/fundamentals/block-wrapper.md
index 39c80262d7bcbe..98c435f6ebe2f7 100644
--- a/docs/getting-started/fundamentals/block-wrapper.md
+++ b/docs/getting-started/fundamentals/block-wrapper.md
@@ -102,7 +102,7 @@ The [example block](https://github.com/WordPress/block-development-examples/tree
## Dynamic render markup
-In dynamic blocks, where the font-end markup is rendered server-side, you can utilize the [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) function to output the necessary classes and attributes just like you would use `useBlockProps.save()` in the `save` function. (See [example](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11))
+In dynamic blocks, where the front-end markup is rendered server-side, you can utilize the [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) function to output the necessary classes and attributes just like you would use `useBlockProps.save()` in the `save` function. (See [example](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11))
```php
>
From 3be25a1db5915a94fe755f078ed35b63ebf7cc91 Mon Sep 17 00:00:00 2001
From: James Koster
Date: Tue, 13 Aug 2024 20:52:40 +0100
Subject: [PATCH 072/126] Apply minimal variant to pagination dropdown (#63815)
Co-authored-by: jameskoster
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: t-hamano
Co-authored-by: ntsekouras
Co-authored-by: swissspidy
Co-authored-by: jasmussen
---
.../components/dataviews-pagination/index.tsx | 51 +++++++++++++------
.../dataviews-pagination/style.scss | 13 +++--
2 files changed, 44 insertions(+), 20 deletions(-)
diff --git a/packages/dataviews/src/components/dataviews-pagination/index.tsx b/packages/dataviews/src/components/dataviews-pagination/index.tsx
index f8ebf41469d949..f022b382cdb70d 100644
--- a/packages/dataviews/src/components/dataviews-pagination/index.tsx
+++ b/packages/dataviews/src/components/dataviews-pagination/index.tsx
@@ -21,10 +21,31 @@ function DataViewsPagination() {
onChangeView,
paginationInfo: { totalItems = 0, totalPages },
} = useContext( DataViewsContext );
+
if ( ! totalItems || ! totalPages ) {
return null;
}
+
const currentPage = view.page ?? 1;
+ const pageSelectOptions = Array.from( Array( totalPages ) ).map(
+ ( _, i ) => {
+ const page = i + 1;
+ return {
+ value: page.toString(),
+ label: page.toString(),
+ 'aria-label':
+ currentPage === page
+ ? sprintf(
+ // translators: Current page number in total number of pages
+ __( 'Page %1$s of %2$s' ),
+ currentPage,
+ totalPages
+ )
+ : page.toString(),
+ };
+ }
+ );
+
return (
!! totalItems &&
totalPages !== 1 && (
@@ -37,37 +58,35 @@ function DataViewsPagination() {
{ createInterpolateElement(
sprintf(
- // translators: %s: Total number of pages.
- _x( 'Page of %s', 'paging' ),
+ // translators: 1: Current page number, 2: Total number of pages.
+ _x(
+ 'Page
%1$sof %2$s
',
+ 'paging'
+ ),
+ ' ',
totalPages
),
{
- CurrentPageControl: (
+ div:
,
+ CurrentPage: (
{
- const page = i + 1;
- return {
- value: page.toString(),
- label: page.toString(),
- };
- } ) }
+ value={ currentPage.toString() }
+ options={ pageSelectOptions }
onChange={ ( newValue ) => {
onChangeView( {
...view,
page: +newValue,
} );
} }
- size="compact"
+ size="small"
__nextHasNoMarginBottom
+ variant="minimal"
/>
),
}
diff --git a/packages/dataviews/src/components/dataviews-pagination/style.scss b/packages/dataviews/src/components/dataviews-pagination/style.scss
index 4e754ab90fa54a..16f064cc3a5178 100644
--- a/packages/dataviews/src/components/dataviews-pagination/style.scss
+++ b/packages/dataviews/src/components/dataviews-pagination/style.scss
@@ -5,17 +5,22 @@
background-color: $white;
padding: $grid-unit-15 $grid-unit-60;
border-top: $border-width solid $gray-100;
- color: $gray-700;
flex-shrink: 0;
transition: padding ease-out 0.1s;
@include reduce-motion("transition");
}
-.dataviews-pagination__page-selection {
+.dataviews-pagination__page-select {
font-size: 11px;
- text-transform: uppercase;
font-weight: 500;
- color: $gray-900;
+ text-transform: uppercase;
+
+ @include break-small() {
+ .components-select-control__input {
+ font-size: 11px !important;
+ font-weight: 500;
+ }
+ }
}
/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */
From 562fff19e73a1416bdc533ef372b8117beeed436 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Wed, 14 Aug 2024 04:53:08 +0900
Subject: [PATCH 073/126] Start adding lint rules for 40px default size
(#64410)
* Start adding lint rules for 40px default size
* Make stricter
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.eslintrc.js | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 6143d62c28bc07..0dc184eabb8b04 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -278,7 +278,6 @@ module.exports = {
},
},
{
- // Temporary rules until we're ready to officially deprecate the bottom margins.
files: [ 'packages/*/src/**/*.[tj]s?(x)' ],
excludedFiles: [
'packages/components/src/**/@(test|stories)/**',
@@ -289,6 +288,7 @@ module.exports = {
'error',
...restrictedSyntax,
...restrictedSyntaxComponents,
+ // Temporary rules until we're ready to officially deprecate the bottom margins.
...[
'BaseControl',
'CheckboxControl',
@@ -309,6 +309,19 @@ module.exports = {
componentName +
' should have the `__nextHasNoMarginBottom` prop to opt-in to the new margin-free styles.',
} ) ),
+ // Temporary rules until we're ready to officially default to the new size.
+ ...[
+ 'BorderBoxControl',
+ 'BorderControl',
+ 'DimensionControl',
+ 'FontSizePicker',
+ ].map( ( componentName ) => ( {
+ // Falsy `__next40pxDefaultSize` without a non-default `size` prop.
+ selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"][value.expression.value!=false])):not(:has(JSXAttribute[name.name="size"][value.value!="default"]))`,
+ message:
+ componentName +
+ ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
+ } ) ),
],
},
},
From 40b51b8619e120c96c3dfc6844ed72f3c84e2ca4 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Wed, 14 Aug 2024 05:28:37 +0900
Subject: [PATCH 074/126] QueryControls: Default to new 40px size (#64457)
* QueryControls: Default to new 40px size
* Add changelog
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/query-controls/index.tsx | 11 +++++------
packages/components/src/query-controls/types.ts | 3 ++-
3 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 761e6604a127a2..6b95e23818c864 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -11,6 +11,7 @@
### Enhancements
- `Composite`: improve Storybook examples and add interactive controls ([#64397](https://github.com/WordPress/gutenberg/pull/64397)).
+- `QueryControls`: Default to new 40px size ([#64457](https://github.com/WordPress/gutenberg/pull/64457)).
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
diff --git a/packages/components/src/query-controls/index.tsx b/packages/components/src/query-controls/index.tsx
index 3557335ebac5a0..452dd303c778bb 100644
--- a/packages/components/src/query-controls/index.tsx
+++ b/packages/components/src/query-controls/index.tsx
@@ -60,7 +60,6 @@ function isMultipleCategorySelection(
* ```
*/
export function QueryControls( {
- __next40pxDefaultSize = false,
authorList,
selectedAuthorId,
numberOfItems,
@@ -82,7 +81,7 @@ export function QueryControls( {
onOrderChange && onOrderByChange && (
Date: Wed, 14 Aug 2024 06:32:10 +0900
Subject: [PATCH 075/126] TextControl: Add lint rule for 40px size prop usage
(#64455)
* TextControl: Add lint rule for 40px size prop usage
* Fixup
* Fixup formatting
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.eslintrc.js | 8 ++++++++
.../src/components/date-format-picker/index.js | 2 ++
.../src/components/media-upload/index.native.js | 2 ++
.../src/components/url-popover/image-url-input-ui.js | 4 ++++
packages/block-library/src/button/edit.js | 2 ++
packages/block-library/src/form-input/edit.js | 2 ++
packages/block-library/src/form/edit.js | 2 ++
packages/block-library/src/image/image.js | 2 ++
.../src/navigation/edit/navigation-menu-name-control.js | 2 ++
packages/block-library/src/post-comment/edit.js | 2 ++
packages/block-library/src/post-featured-image/edit.js | 2 ++
packages/block-library/src/post-terms/edit.js | 2 ++
packages/block-library/src/post-title/edit.js | 2 ++
packages/block-library/src/social-link/edit.js | 4 ++++
.../src/template-part/edit/advanced-controls.js | 2 ++
packages/block-library/src/video/tracks-editor.js | 4 ++++
.../src/components/sidebar-dataviews/add-new-view.js | 2 ++
.../components/sidebar-dataviews/custom-dataviews-list.js | 2 ++
.../src/components/post-publish-panel/postpublish.js | 2 ++
packages/editor/src/components/post-slug/index.js | 2 ++
.../post-taxonomies/hierarchical-term-selector.js | 2 ++
.../reusable-block-convert-button.js | 2 ++
22 files changed, 56 insertions(+)
diff --git a/.eslintrc.js b/.eslintrc.js
index 0dc184eabb8b04..eb2d2db47e4cab 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -322,6 +322,14 @@ module.exports = {
componentName +
' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
} ) ),
+ // Temporary rules until all existing components have the `__next40pxDefaultSize` prop.
+ ...[ 'TextControl' ].map( ( componentName ) => ( {
+ // Not strict. Allows pre-existing __next40pxDefaultSize={ false } usage until they are all manually updated.
+ selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"])):not(:has(JSXAttribute[name.name="size"]))`,
+ message:
+ componentName +
+ ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
+ } ) ),
],
},
},
diff --git a/packages/block-editor/src/components/date-format-picker/index.js b/packages/block-editor/src/components/date-format-picker/index.js
index 15beec4ac6ed54..63c977b111e01c 100644
--- a/packages/block-editor/src/components/date-format-picker/index.js
+++ b/packages/block-editor/src/components/date-format-picker/index.js
@@ -149,6 +149,8 @@ function NonDefaultControls( { format, onChange } ) {
/>
{ isCustom && (
{ isLinkTag && (
{
) }
/>
(
diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js
index c9f4645e5e8654..675580c71c1d7b 100644
--- a/packages/block-library/src/post-featured-image/edit.js
+++ b/packages/block-library/src/post-featured-image/edit.js
@@ -230,6 +230,8 @@ export default function PostFeaturedImageEdit( {
checked={ linkTarget === '_blank' }
/>
onChange( {
diff --git a/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js b/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js
index aabb49c14a2ff7..69cca49fd84563 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js
@@ -78,6 +78,8 @@ function AddNewItemModalContent( { type, setIsAdding } ) {
>
{ showFilter && (
Date: Wed, 14 Aug 2024 07:15:24 +0900
Subject: [PATCH 076/126] Deprecate bottom margin on BaseControl-based
components (#64408)
* BaseControl: Deprecate bottom margin
* Propagate to components
* Missed spots
* Add changelog
* List all affected components in changelog
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 17 ++++++++++
.../components/src/base-control/index.tsx | 13 +++++++
packages/components/src/base-control/types.ts | 7 ++++
.../components/src/checkbox-control/index.tsx | 1 +
.../checkbox-control/stories/index.story.tsx | 1 +
.../src/checkbox-control/test/index.tsx | 9 ++++-
.../components/src/combobox-control/index.tsx | 1 +
.../combobox-control/stories/index.story.tsx | 7 ++--
.../src/combobox-control/test/index.tsx | 6 +++-
.../src/dimension-control/index.tsx | 34 +++++++++++++------
.../dimension-control/stories/index.story.tsx | 1 +
.../test/__snapshots__/index.test.js.snap | 16 ---------
.../src/dimension-control/test/index.test.js | 6 +++-
.../src/focal-point-picker/index.tsx | 1 +
.../stories/index.story.tsx | 3 ++
.../src/focal-point-picker/test/index.tsx | 6 +++-
.../components/src/range-control/index.tsx | 1 +
.../src/range-control/stories/index.story.tsx | 7 ++++
.../src/range-control/test/index.tsx | 8 ++++-
.../components/src/search-control/index.tsx | 11 +++---
.../search-control/stories/index.story.tsx | 1 +
.../src/search-control/test/index.tsx | 1 +
.../components/src/select-control/index.tsx | 1 +
.../select-control/stories/index.story.tsx | 13 ++++---
.../select-control/test/select-control.tsx | 18 ++++++----
.../components/src/text-control/index.tsx | 1 +
.../src/text-control/stories/index.story.tsx | 4 ++-
.../src/text-control/test/text-control.tsx | 6 +++-
.../components/src/textarea-control/index.tsx | 1 +
.../textarea-control/stories/index.story.tsx | 1 +
.../components/src/toggle-control/index.tsx | 9 +++++
.../toggle-control/stories/index.story.tsx | 1 +
.../src/toggle-control/test/index.tsx | 8 ++++-
.../test/__snapshots__/index.tsx.snap | 16 ---------
.../src/toggle-group-control/test/index.tsx | 6 +++-
.../toggle-group-control/component.tsx | 1 +
packages/components/src/tree-select/index.tsx | 21 +++++++++---
.../src/tree-select/stories/index.story.tsx | 1 +
38 files changed, 191 insertions(+), 75 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 6b95e23818c864..36b1c8fcc55d00 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,23 @@
## Unreleased
+### Deprecations
+
+- Deprecate bottom margin on the following `BaseControl`-based components. Set the `__nextHasNoMarginBottom` prop to true to start opting into the new styles, which will become the default in a future version ([#64408](https://github.com/WordPress/gutenberg/pull/64408)).
+ - `BaseControl`
+ - `CheckboxControl`
+ - `ComboboxControl`
+ - `DimensionControl`
+ - `FocalPointPicker`
+ - `RangeControl`
+ - `SearchControl`
+ - `SelectControl`
+ - `TextControl`
+ - `TextareaControl`
+ - `ToggleControl`
+ - `ToggleGroupControl`
+ - `TreeSelect`
+
### New Features
- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).
diff --git a/packages/components/src/base-control/index.tsx b/packages/components/src/base-control/index.tsx
index 77899b6480daed..423636a92cd5f0 100644
--- a/packages/components/src/base-control/index.tsx
+++ b/packages/components/src/base-control/index.tsx
@@ -7,6 +7,7 @@ import type { ForwardedRef } from 'react';
/**
* WordPress dependencies
*/
+import deprecated from '@wordpress/deprecated';
import { forwardRef } from '@wordpress/element';
/**
@@ -31,6 +32,7 @@ const UnconnectedBaseControl = (
) => {
const {
__nextHasNoMarginBottom = false,
+ __associatedWPComponentName = 'BaseControl',
id,
label,
hideLabelFromVision = false,
@@ -39,6 +41,17 @@ const UnconnectedBaseControl = (
children,
} = useContextSystem( props, 'BaseControl' );
+ if ( ! __nextHasNoMarginBottom ) {
+ deprecated(
+ `Bottom margin styles for wp.components.${ __associatedWPComponentName }`,
+ {
+ since: '6.7',
+ version: '7.0',
+ hint: 'Set the `__nextHasNoMarginBottom` prop to true to start opting into the new styles, which will become the default in a future version.',
+ }
+ );
+ }
+
return (
= DefaultTemplate.bind(
{}
);
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Is author',
help: 'Is the user an author or not?',
};
diff --git a/packages/components/src/checkbox-control/test/index.tsx b/packages/components/src/checkbox-control/test/index.tsx
index 899a9b100015b3..547f479184e862 100644
--- a/packages/components/src/checkbox-control/test/index.tsx
+++ b/packages/components/src/checkbox-control/test/index.tsx
@@ -20,13 +20,20 @@ const noop = () => {};
const getInput = () => screen.getByRole( 'checkbox' ) as HTMLInputElement;
const CheckboxControl = ( props: Omit< CheckboxControlProps, 'onChange' > ) => {
- return ;
+ return (
+
+ );
};
const ControlledCheckboxControl = ( { onChange }: CheckboxControlProps ) => {
const [ isChecked, setChecked ] = useState( false );
return (
{
setChecked( value );
diff --git a/packages/components/src/combobox-control/index.tsx b/packages/components/src/combobox-control/index.tsx
index e3b1434be8c7c2..fc3ecccf0b6599 100644
--- a/packages/components/src/combobox-control/index.tsx
+++ b/packages/components/src/combobox-control/index.tsx
@@ -320,6 +320,7 @@ function ComboboxControl( props: ComboboxControlProps ) {
= ( {
};
export const Default = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
allowReset: false,
label: 'Select a country',
options: countryOptions,
@@ -135,8 +136,7 @@ const optionsWithDisabledOptions = countryOptions.map( ( option, index ) => ( {
} ) );
WithDisabledOptions.args = {
- allowReset: false,
- label: 'Select a country',
+ ...Default.args,
options: optionsWithDisabledOptions,
};
@@ -148,8 +148,7 @@ WithDisabledOptions.args = {
export const NotExpandOnFocus = Template.bind( {} );
NotExpandOnFocus.args = {
- allowReset: false,
- label: 'Select a country',
+ ...Default.args,
options: countryOptions,
expandOnFocus: false,
};
diff --git a/packages/components/src/combobox-control/test/index.tsx b/packages/components/src/combobox-control/test/index.tsx
index 76ce9cc4724c54..adc76590c24538 100644
--- a/packages/components/src/combobox-control/test/index.tsx
+++ b/packages/components/src/combobox-control/test/index.tsx
@@ -12,7 +12,7 @@ import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
-import ComboboxControl from '..';
+import _ComboboxControl from '..';
import type { ComboboxControlOption, ComboboxControlProps } from '../types';
const timezones = [
@@ -57,6 +57,10 @@ const getAllOptions = () => screen.getAllByRole( 'option' );
const getOptionSearchString = ( option: ComboboxControlOption ) =>
option.label.substring( 0, 11 );
+const ComboboxControl = ( props: ComboboxControlProps ) => {
+ return <_ComboboxControl { ...props } __nextHasNoMarginBottom />;
+};
+
const ControlledComboboxControl = ( {
value: valueProp,
onChange,
diff --git a/packages/components/src/dimension-control/index.tsx b/packages/components/src/dimension-control/index.tsx
index 114ebe987dd35d..52662f31c3f24c 100644
--- a/packages/components/src/dimension-control/index.tsx
+++ b/packages/components/src/dimension-control/index.tsx
@@ -16,6 +16,15 @@ import SelectControl from '../select-control';
import sizesTable, { findSizeBySlug } from './sizes';
import type { DimensionControlProps, Size } from './types';
import type { SelectControlSingleSelectionProps } from '../select-control/types';
+import { ContextSystemProvider } from '../context';
+
+const CONTEXT_VALUE = {
+ BaseControl: {
+ // Temporary during deprecation grace period: Overrides the underlying `__associatedWPComponentName`
+ // via the context system to override the value set by SelectControl.
+ _overrides: { __associatedWPComponentName: 'DimensionControl' },
+ },
+};
/**
* `DimensionControl` is a component designed to provide a UI to control spacing and/or dimensions.
@@ -87,16 +96,21 @@ export function DimensionControl( props: DimensionControlProps ) {
);
return (
-
+
+
+
);
}
diff --git a/packages/components/src/dimension-control/stories/index.story.tsx b/packages/components/src/dimension-control/stories/index.story.tsx
index 33d5bad4ff4b19..3a6da44f461164 100644
--- a/packages/components/src/dimension-control/stories/index.story.tsx
+++ b/packages/components/src/dimension-control/stories/index.story.tsx
@@ -44,6 +44,7 @@ const Template: StoryFn< typeof DimensionControl > = ( args ) => (
export const Default = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Please select a size',
sizes,
};
diff --git a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap
index 5990fbbd4a3f5f..658fe7febc02bc 100644
--- a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap
+++ b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap
@@ -13,10 +13,6 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = `
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -299,10 +295,6 @@ exports[`DimensionControl rendering renders with defaults 1`] = `
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -595,10 +587,6 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`]
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -903,10 +891,6 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`]
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
diff --git a/packages/components/src/dimension-control/test/index.test.js b/packages/components/src/dimension-control/test/index.test.js
index 1d65dd86c7e7c8..1b34d2983ad0f1 100644
--- a/packages/components/src/dimension-control/test/index.test.js
+++ b/packages/components/src/dimension-control/test/index.test.js
@@ -12,7 +12,11 @@ import { plus } from '@wordpress/icons';
/**
* Internal dependencies
*/
-import { DimensionControl } from '../';
+import { DimensionControl as _DimensionControl } from '../';
+
+const DimensionControl = ( props ) => {
+ return <_DimensionControl { ...props } __nextHasNoMarginBottom />;
+};
describe( 'DimensionControl', () => {
const onChangeHandler = jest.fn();
diff --git a/packages/components/src/focal-point-picker/index.tsx b/packages/components/src/focal-point-picker/index.tsx
index 4575108fe6a21d..8f299751be0d48 100644
--- a/packages/components/src/focal-point-picker/index.tsx
+++ b/packages/components/src/focal-point-picker/index.tsx
@@ -251,6 +251,7 @@ export function FocalPointPicker( {
= ( {
};
export const Default = Template.bind( {} );
+Default.args = {
+ __nextHasNoMarginBottom: true,
+};
export const Image = Template.bind( {} );
Image.args = {
diff --git a/packages/components/src/focal-point-picker/test/index.tsx b/packages/components/src/focal-point-picker/test/index.tsx
index 1eccced32c70af..377ba6c4e9e6b3 100644
--- a/packages/components/src/focal-point-picker/test/index.tsx
+++ b/packages/components/src/focal-point-picker/test/index.tsx
@@ -7,12 +7,16 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
-import Picker from '..';
+import _Picker from '..';
import type { FocalPointPickerProps } from '../types';
type Log = { name: string; args: unknown[] };
type EventLogger = ( name: string, args: unknown[] ) => void;
+const Picker = ( props: React.ComponentProps< typeof _Picker > ) => {
+ return <_Picker { ...props } __nextHasNoMarginBottom />;
+};
+
const props: FocalPointPickerProps = {
onChange: jest.fn(),
url: 'test-url',
diff --git a/packages/components/src/range-control/index.tsx b/packages/components/src/range-control/index.tsx
index 5b4ecfa585679b..20d9712df9c251 100644
--- a/packages/components/src/range-control/index.tsx
+++ b/packages/components/src/range-control/index.tsx
@@ -213,6 +213,7 @@ function UnforwardedRangeControl(
return (
= ( { onChange, ...args } ) => {
export const Default: StoryFn< typeof RangeControl > = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
help: 'Please select how transparent you would like this.',
initialPosition: 50,
label: 'Opacity',
@@ -104,6 +105,7 @@ export const WithAnyStep: StoryFn< typeof RangeControl > = ( {
);
};
WithAnyStep.args = {
+ __nextHasNoMarginBottom: true,
label: 'Brightness',
step: 'any',
};
@@ -167,6 +169,7 @@ export const WithIntegerStepAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithIntegerStepAndMarks.args = {
+ __nextHasNoMarginBottom: true,
label: 'Integer Step',
marks: marksBase,
max: 10,
@@ -183,6 +186,7 @@ export const WithDecimalStepAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithDecimalStepAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: [
...marksBase,
{ value: 3.5, label: '3.5' },
@@ -202,6 +206,7 @@ export const WithNegativeMinimumAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithNegativeMinimumAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: marksWithNegatives,
max: 10,
min: -10,
@@ -217,6 +222,7 @@ export const WithNegativeRangeAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithNegativeRangeAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: marksWithNegatives,
max: -1,
min: -10,
@@ -232,6 +238,7 @@ export const WithAnyStepAndMarks: StoryFn< typeof RangeControl > =
MarkTemplate.bind( {} );
WithAnyStepAndMarks.args = {
+ __nextHasNoMarginBottom: true,
marks: marksBase,
max: 10,
min: 0,
diff --git a/packages/components/src/range-control/test/index.tsx b/packages/components/src/range-control/test/index.tsx
index d843b615ed0078..a4c5d8c6f2bc7f 100644
--- a/packages/components/src/range-control/test/index.tsx
+++ b/packages/components/src/range-control/test/index.tsx
@@ -6,7 +6,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
-import RangeControl from '../';
+import _RangeControl from '../';
const getRangeInput = (): HTMLInputElement => screen.getByRole( 'slider' );
const getNumberInput = (): HTMLInputElement => screen.getByRole( 'spinbutton' );
@@ -15,6 +15,12 @@ const getResetButton = (): HTMLButtonElement => screen.getByRole( 'button' );
const fireChangeEvent = ( input: HTMLInputElement, value?: number | string ) =>
fireEvent.change( input, { target: { value } } );
+const RangeControl = (
+ props: React.ComponentProps< typeof _RangeControl >
+) => {
+ return <_RangeControl { ...props } __nextHasNoMarginBottom />;
+};
+
describe( 'RangeControl', () => {
describe( '#render()', () => {
it( 'should trigger change callback with numeric value', () => {
diff --git a/packages/components/src/search-control/index.tsx b/packages/components/src/search-control/index.tsx
index 08cb3b065c904e..aac905e137e025 100644
--- a/packages/components/src/search-control/index.tsx
+++ b/packages/components/src/search-control/index.tsx
@@ -77,10 +77,13 @@ function UnforwardedSearchControl(
const contextValue = useMemo(
() => ( {
- // Overrides the underlying BaseControl `__nextHasNoMarginBottom` via the context system
- // to provide backwards compatibile margin for SearchControl.
- // (In a standard InputControl, the BaseControl `__nextHasNoMarginBottom` is always set to true.)
- BaseControl: { _overrides: { __nextHasNoMarginBottom } },
+ BaseControl: {
+ // Overrides the underlying BaseControl `__nextHasNoMarginBottom` via the context system
+ // to provide backwards compatibile margin for SearchControl.
+ // (In a standard InputControl, the BaseControl `__nextHasNoMarginBottom` is always set to true.)
+ _overrides: { __nextHasNoMarginBottom },
+ __associatedWPComponentName: 'SearchControl',
+ },
// `isBorderless` is still experimental and not a public prop for InputControl yet.
InputBase: { isBorderless: true },
} ),
diff --git a/packages/components/src/search-control/stories/index.story.tsx b/packages/components/src/search-control/stories/index.story.tsx
index 433d3eef655adf..215288bb67c9b6 100644
--- a/packages/components/src/search-control/stories/index.story.tsx
+++ b/packages/components/src/search-control/stories/index.story.tsx
@@ -48,6 +48,7 @@ const Template: StoryFn< typeof SearchControl > = ( {
export const Default = Template.bind( {} );
Default.args = {
help: 'Help text to explain the input.',
+ __nextHasNoMarginBottom: true,
};
/**
diff --git a/packages/components/src/search-control/test/index.tsx b/packages/components/src/search-control/test/index.tsx
index f130cab1b2a7cd..c6637945adcf63 100644
--- a/packages/components/src/search-control/test/index.tsx
+++ b/packages/components/src/search-control/test/index.tsx
@@ -23,6 +23,7 @@ function ControlledSearchControl( {
return (
{
setValue( ...args );
diff --git a/packages/components/src/select-control/index.tsx b/packages/components/src/select-control/index.tsx
index ca9966fc675b86..3686661b8a58dc 100644
--- a/packages/components/src/select-control/index.tsx
+++ b/packages/components/src/select-control/index.tsx
@@ -99,6 +99,7 @@ function UnforwardedSelectControl< V extends string >(
help={ help }
id={ id }
__nextHasNoMarginBottom={ __nextHasNoMarginBottom }
+ __associatedWPComponentName="SelectControl"
>
= ( props ) => {
export const Default = SelectControlWithState.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
options: [
{ value: '', label: 'Select an Option', disabled: true },
{ value: 'a', label: 'Option A' },
@@ -82,9 +83,11 @@ WithLabelAndHelpText.args = {
* As an alternative to the `options` prop, `optgroup`s and `options` can be
* passed in as `children` for more customizeability.
*/
-export const WithCustomChildren: StoryFn< typeof SelectControl > = ( args ) => {
- return (
-
+export const WithCustomChildren = SelectControlWithState.bind( {} );
+WithCustomChildren.args = {
+ __nextHasNoMarginBottom: true,
+ children: (
+ <>
Option 1
Option 2 - Disabled
@@ -97,8 +100,8 @@ export const WithCustomChildren: StoryFn< typeof SelectControl > = ( args ) => {
Option Group 1 - Option 2 - Disabled
-
- );
+ >
+ ),
};
export const Minimal = SelectControlWithState.bind( {} );
diff --git a/packages/components/src/select-control/test/select-control.tsx b/packages/components/src/select-control/test/select-control.tsx
index 0e8a6891087043..47b684cd20e280 100644
--- a/packages/components/src/select-control/test/select-control.tsx
+++ b/packages/components/src/select-control/test/select-control.tsx
@@ -7,7 +7,13 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
-import SelectControl from '..';
+import _SelectControl from '..';
+
+const SelectControl = (
+ props: React.ComponentProps< typeof _SelectControl >
+) => {
+ return <_SelectControl { ...props } __nextHasNoMarginBottom />;
+};
describe( 'SelectControl', () => {
it( 'should not render when no options or children are provided', () => {
@@ -123,7 +129,7 @@ describe( 'SelectControl', () => {
onChange={ onChange }
/>;
- {
} );
it( 'should accept an explicit type argument', () => {
-
+ <_SelectControl< 'narrow' | 'value' >
// @ts-expect-error "string" is not "narrow" or "value"
value="string"
options={ [
@@ -166,7 +172,7 @@ describe( 'SelectControl', () => {
value: ( 'foo' | 'bar' )[]
) => void = () => {};
- {
onChange={ onChange }
/>;
- {
} );
it( 'should accept an explicit type argument', () => {
-
+ <_SelectControl< 'narrow' | 'value' >
multiple
// @ts-expect-error "string" is not "narrow" or "value"
value={ [ 'string' ] }
diff --git a/packages/components/src/text-control/index.tsx b/packages/components/src/text-control/index.tsx
index 1643c5bc37c347..ea2d2c17bb9cf6 100644
--- a/packages/components/src/text-control/index.tsx
+++ b/packages/components/src/text-control/index.tsx
@@ -41,6 +41,7 @@ function UnforwardedTextControl(
return (
= ( {
export const Default: StoryFn< typeof TextControl > = DefaultTemplate.bind(
{}
);
-Default.args = {};
+Default.args = {
+ __nextHasNoMarginBottom: true,
+};
export const WithLabelAndHelpText: StoryFn< typeof TextControl > =
DefaultTemplate.bind( {} );
diff --git a/packages/components/src/text-control/test/text-control.tsx b/packages/components/src/text-control/test/text-control.tsx
index fc048b93992f08..19b17cae443614 100644
--- a/packages/components/src/text-control/test/text-control.tsx
+++ b/packages/components/src/text-control/test/text-control.tsx
@@ -6,7 +6,11 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
-import TextControl from '..';
+import _TextControl from '..';
+
+const TextControl = ( props: React.ComponentProps< typeof _TextControl > ) => {
+ return <_TextControl { ...props } __nextHasNoMarginBottom />;
+};
const noop = () => {};
diff --git a/packages/components/src/textarea-control/index.tsx b/packages/components/src/textarea-control/index.tsx
index 3b96e11b0621b5..e7528510667b75 100644
--- a/packages/components/src/textarea-control/index.tsx
+++ b/packages/components/src/textarea-control/index.tsx
@@ -35,6 +35,7 @@ function UnforwardedTextareaControl(
return (
= ( {
export const Default: StoryFn< typeof TextareaControl > = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Text',
help: 'Enter some text',
};
diff --git a/packages/components/src/toggle-control/index.tsx b/packages/components/src/toggle-control/index.tsx
index 5c64d57d3d0249..d2ee234a9695f8 100644
--- a/packages/components/src/toggle-control/index.tsx
+++ b/packages/components/src/toggle-control/index.tsx
@@ -10,6 +10,7 @@ import clsx from 'clsx';
*/
import { forwardRef } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -48,6 +49,14 @@ function UnforwardedToggleControl(
! __nextHasNoMarginBottom && css( { marginBottom: space( 3 ) } )
);
+ if ( ! __nextHasNoMarginBottom ) {
+ deprecated( 'Bottom margin styles for wp.components.ToggleControl', {
+ since: '6.7',
+ version: '7.0',
+ hint: 'Set the `__nextHasNoMarginBottom` prop to true to start opting into the new styles, which will become the default in a future version.',
+ } );
+ }
+
let describedBy, helpLabel;
if ( help ) {
if ( typeof help === 'function' ) {
diff --git a/packages/components/src/toggle-control/stories/index.story.tsx b/packages/components/src/toggle-control/stories/index.story.tsx
index b8043b8f48e523..97723aa207a394 100644
--- a/packages/components/src/toggle-control/stories/index.story.tsx
+++ b/packages/components/src/toggle-control/stories/index.story.tsx
@@ -48,6 +48,7 @@ const Template: StoryFn< typeof ToggleControl > = ( {
export const Default = Template.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Enable something',
};
diff --git a/packages/components/src/toggle-control/test/index.tsx b/packages/components/src/toggle-control/test/index.tsx
index cc89031d9affa3..b0eec2aca6663d 100644
--- a/packages/components/src/toggle-control/test/index.tsx
+++ b/packages/components/src/toggle-control/test/index.tsx
@@ -6,7 +6,13 @@ import { render, screen } from '@testing-library/react';
/**
* Internal dependencies
*/
-import ToggleControl from '..';
+import _ToggleControl from '..';
+
+const ToggleControl = (
+ props: React.ComponentProps< typeof _ToggleControl >
+) => {
+ return <_ToggleControl { ...props } __nextHasNoMarginBottom />;
+};
describe( 'ToggleControl', () => {
it( 'should label the toggle', () => {
diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
index 81afc7ac67b05f..d055ea5fcc9838 100644
--- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
+++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
@@ -13,10 +13,6 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -349,10 +345,6 @@ exports[`ToggleGroupControl controlled should render correctly with text options
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -573,10 +565,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
@@ -903,10 +891,6 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio
box-sizing: inherit;
}
-.emotion-2 {
- margin-bottom: calc(4px * 2);
-}
-
.components-panel__row .emotion-2 {
margin-bottom: inherit;
}
diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx
index 661bbb9fc37bab..170db01ae523c2 100644
--- a/packages/components/src/toggle-group-control/test/index.tsx
+++ b/packages/components/src/toggle-group-control/test/index.tsx
@@ -15,7 +15,7 @@ import { formatLowercase, formatUppercase } from '@wordpress/icons';
*/
import Button from '../../button';
import {
- ToggleGroupControl,
+ ToggleGroupControl as _ToggleGroupControl,
ToggleGroupControlOption,
ToggleGroupControlOptionIcon,
} from '../index';
@@ -27,6 +27,10 @@ const hoverOutside = async () => {
await hover( document.body, { clientX: 10, clientY: 10 } );
};
+const ToggleGroupControl = ( props: ToggleGroupControlProps ) => {
+ return <_ToggleGroupControl { ...props } __nextHasNoMarginBottom />;
+};
+
const ControlledToggleGroupControl = ( {
value: valueProp,
onChange,
diff --git a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx
index 8138b76505fe50..1c86c93548f6df 100644
--- a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx
+++ b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx
@@ -72,6 +72,7 @@ function UnconnectedToggleGroupControl(
{ ! hideLabelFromVision && (
diff --git a/packages/components/src/tree-select/index.tsx b/packages/components/src/tree-select/index.tsx
index 599dee4402ec72..bd92807bff4cc9 100644
--- a/packages/components/src/tree-select/index.tsx
+++ b/packages/components/src/tree-select/index.tsx
@@ -10,6 +10,15 @@ import { decodeEntities } from '@wordpress/html-entities';
import { SelectControl } from '../select-control';
import type { TreeSelectProps, Tree, Truthy } from './types';
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
+import { ContextSystemProvider } from '../context';
+
+const CONTEXT_VALUE = {
+ BaseControl: {
+ // Temporary during deprecation grace period: Overrides the underlying `__associatedWPComponentName`
+ // via the context system to override the value set by SelectControl.
+ _overrides: { __associatedWPComponentName: 'TreeSelect' },
+ },
+};
function getSelectOptions(
tree: Tree[],
@@ -91,11 +100,13 @@ export function TreeSelect( props: TreeSelectProps ) {
}, [ noOptionLabel, tree ] );
return (
-
+
+
+
);
}
diff --git a/packages/components/src/tree-select/stories/index.story.tsx b/packages/components/src/tree-select/stories/index.story.tsx
index 0a4212dc791227..33103786bbc541 100644
--- a/packages/components/src/tree-select/stories/index.story.tsx
+++ b/packages/components/src/tree-select/stories/index.story.tsx
@@ -48,6 +48,7 @@ const TreeSelectWithState: StoryFn< typeof TreeSelect > = ( props ) => {
export const Default = TreeSelectWithState.bind( {} );
Default.args = {
+ __nextHasNoMarginBottom: true,
label: 'Label Text',
noOptionLabel: 'No parent page',
help: 'Help text to explain the select control.',
From e0760706272e4d6af3f1b0c7f90ab4ea9e305256 Mon Sep 17 00:00:00 2001
From: Amit Raj <77401999+amitraj2203@users.noreply.github.com>
Date: Wed, 14 Aug 2024 11:16:04 +0530
Subject: [PATCH 077/126] chore: Add label prop to SizeControl component
(#64428)
Co-authored-by: amitraj2203
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
.../src/components/global-styles/size-control/index.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/packages/edit-site/src/components/global-styles/size-control/index.js b/packages/edit-site/src/components/global-styles/size-control/index.js
index 28fa64c643cbbd..06ea0bb5617e31 100644
--- a/packages/edit-site/src/components/global-styles/size-control/index.js
+++ b/packages/edit-site/src/components/global-styles/size-control/index.js
@@ -1,7 +1,6 @@
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
@@ -26,7 +25,7 @@ function SizeControl( {
...props
} ) {
const { baseControlProps } = useBaseControlProps( props );
- const { value, onChange, fallbackValue, disabled } = props;
+ const { value, onChange, fallbackValue, disabled, label } = props;
const units = useCustomUnits( {
availableUnits: DEFAULT_UNITS,
@@ -55,7 +54,7 @@ function SizeControl( {
Date: Wed, 14 Aug 2024 08:23:24 +0100
Subject: [PATCH 078/126] Zoon Out: Fix scale calculations (#64478)
---
packages/block-editor/src/components/iframe/index.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js
index e7af77920ea127..3b0bce6d56b403 100644
--- a/packages/block-editor/src/components/iframe/index.js
+++ b/packages/block-editor/src/components/iframe/index.js
@@ -242,8 +242,10 @@ function Iframe( {
const isZoomedOut = scale !== 1;
useEffect( () => {
- prevContainerWidth.current = containerWidth;
- }, [ containerWidth ] );
+ if ( ! isZoomedOut ) {
+ prevContainerWidth.current = containerWidth;
+ }
+ }, [ containerWidth, isZoomedOut ] );
const disabledRef = useDisabled( { isDisabled: ! readonly } );
const bodyRef = useMergeRefs( [
From c20273736e30e2bfc54f82faaeaad4912b25cd29 Mon Sep 17 00:00:00 2001
From: Gutenberg Repository Automation
Date: Wed, 14 Aug 2024 12:18:10 +0000
Subject: [PATCH 079/126] Bump plugin version to 19.0.0
---
gutenberg.php | 2 +-
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/gutenberg.php b/gutenberg.php
index 6ec0a56f00e74d..66f0aa31a65baa 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -5,7 +5,7 @@
* Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality.
* Requires at least: 6.5
* Requires PHP: 7.2
- * Version: 19.0.0-rc.1
+ * Version: 19.0.0
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/package-lock.json b/package-lock.json
index 471d7b845d24f8..ddfec3a5dddc63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gutenberg",
- "version": "19.0.0-rc.1",
+ "version": "19.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "gutenberg",
- "version": "19.0.0-rc.1",
+ "version": "19.0.0",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index e4466eb4f470e1..ee78f197a43e28 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "19.0.0-rc.1",
+ "version": "19.0.0",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
From 54269c00f74805533b5fec9bf14b84d18f8d3ae9 Mon Sep 17 00:00:00 2001
From: Gutenberg Repository Automation
Date: Wed, 14 Aug 2024 12:41:28 +0000
Subject: [PATCH 080/126] Update Changelog for 19.0.0
---
changelog.txt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/changelog.txt b/changelog.txt
index e85895547e87d1..748df8da3484c7 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,7 +1,6 @@
== Changelog ==
-= 19.0.0-rc.1 =
-
+= 19.0.0 =
## Changelog
@@ -368,6 +367,8 @@ The following contributors merged PRs in this release:
@aaronrobertshaw @adamsilverstein @afercia @akasunil @Aljullu @amitraj2203 @andrewserong @carolinan @cbravobernal @Chrico @ciampo @creativecoder @DaniGuardiola @DAreRodz @djcowan @ellatrix @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @kebbet @kmanijak @Mamaduka @matiasbenedetto @meteorlxy @mikachan @mirka @mtias @ndiego @noisysocks @oandregal @ramonjd @richtabor @Rishit30G @ryanwelcher @SantosGuillamot @scruffian @shail-mehta @simison @stokesman @t-hamano @talldan @tomdevisser @tomjn @tyxla @up1512001 @wzieba @youknowriad
+
+
= 18.9.0 =
## Changelog
From b533bfd6d00c2aa99341568d5328a6ca6f826823 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Wed, 14 Aug 2024 15:32:14 +0200
Subject: [PATCH 081/126] `Composite`: use internal context to consume
composite store (#64493)
* Create CompositeContext
* Use context to pass store to composite subcomponents
* CHANGELOG
* Remove double dot
* Export `Context`, add basic docs
* Update CHANGELOG
---
Co-authored-by: ciampo
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 4 +-
packages/components/src/composite/README.md | 4 +
packages/components/src/composite/context.ts | 14 +++
packages/components/src/composite/index.tsx | 95 ++++++++++++++++++--
packages/components/src/composite/types.ts | 9 ++
5 files changed, 116 insertions(+), 10 deletions(-)
create mode 100644 packages/components/src/composite/context.ts
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 36b1c8fcc55d00..594bb47e187d51 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -23,11 +23,13 @@
- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).
- `Composite`: add `Hover` and `Typeahead` subcomponents ([#64399](https://github.com/WordPress/gutenberg/pull/64399)).
-- `Composite`: export `useCompositeStore, add focus-related props to `Composite`and`Composite.Item` subcomponents ([#64450](https://github.com/WordPress/gutenberg/pull/64450)).
+- `Composite`: export `useCompositeStore`, add focus-related props to `Composite`and`Composite.Item` subcomponents ([#64450](https://github.com/WordPress/gutenberg/pull/64450)).
+- `Composite`: add `Context` subcomponent ([#64493](https://github.com/WordPress/gutenberg/pull/64493)).
### Enhancements
- `Composite`: improve Storybook examples and add interactive controls ([#64397](https://github.com/WordPress/gutenberg/pull/64397)).
+- `Composite`: use internal context to forward the composite store to sub-components ([#64493](https://github.com/WordPress/gutenberg/pull/64493)).
- `QueryControls`: Default to new 40px size ([#64457](https://github.com/WordPress/gutenberg/pull/64457)).
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
diff --git a/packages/components/src/composite/README.md b/packages/components/src/composite/README.md
index 3670e31b01e9df..76e345b16d13fa 100644
--- a/packages/components/src/composite/README.md
+++ b/packages/components/src/composite/README.md
@@ -315,3 +315,7 @@ Allows the component to be rendered as a different HTML element or React compone
The contents of the component.
- Required: no
+
+### `Composite.Context`
+
+The React context used by the composite components. It can be used by to access the composite store, and to forward the context when composite sub-components are rendered across portals (ie. `SlotFill` components) that would not otherwise forward the context to the `Fill` children.
diff --git a/packages/components/src/composite/context.ts b/packages/components/src/composite/context.ts
new file mode 100644
index 00000000000000..69a052c5bfba19
--- /dev/null
+++ b/packages/components/src/composite/context.ts
@@ -0,0 +1,14 @@
+/**
+ * WordPress dependencies
+ */
+import { createContext, useContext } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import type { CompositeContextProps } from './types';
+
+export const CompositeContext =
+ createContext< CompositeContextProps >( undefined );
+
+export const useCompositeContext = () => useContext( CompositeContext );
diff --git a/packages/components/src/composite/index.tsx b/packages/components/src/composite/index.tsx
index f5d92330cada3c..0bfcec2bf76600 100644
--- a/packages/components/src/composite/index.tsx
+++ b/packages/components/src/composite/index.tsx
@@ -16,12 +16,13 @@ import * as Ariakit from '@ariakit/react';
/**
* WordPress dependencies
*/
-import { forwardRef } from '@wordpress/element';
+import { useMemo, forwardRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import type { WordPressComponentProps } from '../context';
+import { CompositeContext, useCompositeContext } from './context';
import type {
CompositeStoreProps,
CompositeProps,
@@ -72,7 +73,14 @@ const Group = forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeGroupProps, 'div', false >
>( function CompositeGroup( props, ref ) {
- return ;
+ const context = useCompositeContext();
+ return (
+
+ );
} );
Group.displayName = 'Composite.Group';
@@ -80,7 +88,14 @@ const GroupLabel = forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeGroupLabelProps, 'div', false >
>( function CompositeGroupLabel( props, ref ) {
- return ;
+ const context = useCompositeContext();
+ return (
+
+ );
} );
GroupLabel.displayName = 'Composite.GroupLabel';
@@ -88,7 +103,14 @@ const Item = forwardRef<
HTMLButtonElement,
WordPressComponentProps< CompositeItemProps, 'button', false >
>( function CompositeItem( props, ref ) {
- return ;
+ const context = useCompositeContext();
+ return (
+
+ );
} );
Item.displayName = 'Composite.Item';
@@ -96,7 +118,14 @@ const Row = forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeRowProps, 'div', false >
>( function CompositeRow( props, ref ) {
- return ;
+ const context = useCompositeContext();
+ return (
+
+ );
} );
Row.displayName = 'Composite.Row';
@@ -104,7 +133,14 @@ const Hover = forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeHoverProps, 'div', false >
>( function CompositeHover( props, ref ) {
- return ;
+ const context = useCompositeContext();
+ return (
+
+ );
} );
Hover.displayName = 'Composite.Hover';
@@ -112,7 +148,14 @@ const Typeahead = forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeTypeaheadProps, 'div', false >
>( function CompositeTypeahead( props, ref ) {
- return ;
+ const context = useCompositeContext();
+ return (
+
+ );
} );
Typeahead.displayName = 'Composite.Typeahead';
@@ -136,9 +179,28 @@ export const Composite = Object.assign(
forwardRef<
HTMLDivElement,
WordPressComponentProps< CompositeProps, 'div', false >
- >( function Composite( { disabled = false, ...props }, ref ) {
+ >( function Composite(
+ { children, store, disabled = false, ...props },
+ ref
+ ) {
+ const contextValue = useMemo(
+ () => ( {
+ store,
+ } ),
+ [ store ]
+ );
+
return (
-
+
+
+ { children }
+
+
);
} ),
{
@@ -260,5 +322,20 @@ export const Composite = Object.assign(
* ```
*/
Typeahead,
+ /**
+ * The React context used by the composite components. It can be used by
+ * to access the composite store, and to forward the context when composite
+ * sub-components are rendered across portals (ie. `SlotFill` components)
+ * that would not otherwise forward the context to the `Fill` children.
+ *
+ * @example
+ * ```jsx
+ * import { Composite } from '@wordpress/components';
+ * import { useContext } from '@wordpress/element';
+ *
+ * const compositeContext = useContext( Composite.Context );
+ * ```
+ */
+ Context: CompositeContext,
}
);
diff --git a/packages/components/src/composite/types.ts b/packages/components/src/composite/types.ts
index 5afe410f7582ba..05a2b8473eb349 100644
--- a/packages/components/src/composite/types.ts
+++ b/packages/components/src/composite/types.ts
@@ -3,6 +3,15 @@
*/
import type * as Ariakit from '@ariakit/react';
+export type CompositeContextProps =
+ | {
+ /**
+ * Object returned by the `useCompositeStore` hook.
+ */
+ store: Ariakit.CompositeStore;
+ }
+ | undefined;
+
export type CompositeStoreProps = {
/**
* The current active item `id`. The active item is the element within the
From 00d1f8afac2e3b9764dc28261462b72794ae5ce0 Mon Sep 17 00:00:00 2001
From: Luis Herranz
Date: Wed, 14 Aug 2024 17:04:04 +0200
Subject: [PATCH 082/126] Add missing changes to the changelog for the PR
#62734 (#64507)
Co-authored-by: luisherranz
Co-authored-by: SantosGuillamot
---
packages/interactivity/CHANGELOG.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md
index 6eb664984e1e53..52707b70ba5a7b 100644
--- a/packages/interactivity/CHANGELOG.md
+++ b/packages/interactivity/CHANGELOG.md
@@ -4,14 +4,24 @@
## 6.5.0 (2024-08-07)
+### Enhancements
+
+- Refactor internal proxy and signals system ([#62734](https://github.com/WordPress/gutenberg/pull/62734)).
+
+### Bug Fixes
+
+- Prevent overriding of existing properties on state and context after a client-side navigation ([#62734](https://github.com/WordPress/gutenberg/pull/62734)).
+
## 6.4.0 (2024-07-24)
## 6.3.0 (2024-07-10)
## 6.2.0 (2024-06-26)
+
### Enhancements
- Export `splitTask` function from `@wordpress/interactivity` package to facilitate yielding to the main thread. See example in [async actions](https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/interactivity-api/api-reference.md#async-actions) documentation. ([#62665](https://github.com/WordPress/gutenberg/pull/62665))
+
## 6.1.0 (2024-06-15)
## 6.0.0 (2024-05-31)
From 93b473840667751c655a9afbed2ebd2c1ade522e Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Wed, 14 Aug 2024 17:29:41 +0200
Subject: [PATCH 083/126] `Composite` v2: undo stabilizing new version (#64510)
* Remove exports from `@wordpress/components` package
* Update Storybook
* Update CHANGELOG
* README
---
Co-authored-by: ciampo
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 13 ++++++-------
packages/components/src/composite/README.md | 4 ++++
.../src/composite/legacy/stories/index.story.tsx | 3 +--
.../src/composite/stories/index.story.tsx | 3 ++-
packages/components/src/index.ts | 1 -
5 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 594bb47e187d51..7b3a2aea460d9f 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -19,13 +19,6 @@
- `ToggleGroupControl`
- `TreeSelect`
-### New Features
-
-- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).
-- `Composite`: add `Hover` and `Typeahead` subcomponents ([#64399](https://github.com/WordPress/gutenberg/pull/64399)).
-- `Composite`: export `useCompositeStore`, add focus-related props to `Composite`and`Composite.Item` subcomponents ([#64450](https://github.com/WordPress/gutenberg/pull/64450)).
-- `Composite`: add `Context` subcomponent ([#64493](https://github.com/WordPress/gutenberg/pull/64493)).
-
### Enhancements
- `Composite`: improve Storybook examples and add interactive controls ([#64397](https://github.com/WordPress/gutenberg/pull/64397)).
@@ -34,6 +27,12 @@
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
+### Internal
+
+- `Composite` v2: add `Hover` and `Typeahead` subcomponents ([#64399](https://github.com/WordPress/gutenberg/pull/64399)).
+- `Composite` v2: add focus-related props to `Composite`and`Composite.Item` subcomponents ([#64450](https://github.com/WordPress/gutenberg/pull/64450)).
+- `Composite` v2: add `Context` subcomponent ([#64493](https://github.com/WordPress/gutenberg/pull/64493)).
+
## 28.5.0 (2024-08-07)
### Bug Fixes
diff --git a/packages/components/src/composite/README.md b/packages/components/src/composite/README.md
index 76e345b16d13fa..35881d815cf1bc 100644
--- a/packages/components/src/composite/README.md
+++ b/packages/components/src/composite/README.md
@@ -1,5 +1,9 @@
# `Composite`
+
+This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
+
+
`Composite` provides a single tab stop on the page and allows navigation through the focusable descendants with arrow keys. This abstract component is based on the [WAI-ARIA Composite Role](https://w3c.github.io/aria/#composite).
## Usage
diff --git a/packages/components/src/composite/legacy/stories/index.story.tsx b/packages/components/src/composite/legacy/stories/index.story.tsx
index 1b8e07e9bbf560..e46d656a16810e 100644
--- a/packages/components/src/composite/legacy/stories/index.story.tsx
+++ b/packages/components/src/composite/legacy/stories/index.story.tsx
@@ -15,8 +15,7 @@ import {
import { UseCompositeStatePlaceholder, transform } from './utils';
const meta: Meta< typeof UseCompositeStatePlaceholder > = {
- title: 'Components (Deprecated)/Composite (Unstable)',
- id: 'components-composite-unstable',
+ title: 'Components/Composite',
component: UseCompositeStatePlaceholder,
subcomponents: {
Composite,
diff --git a/packages/components/src/composite/stories/index.story.tsx b/packages/components/src/composite/stories/index.story.tsx
index 405962b92a761c..034e1d6721f7bd 100644
--- a/packages/components/src/composite/stories/index.story.tsx
+++ b/packages/components/src/composite/stories/index.story.tsx
@@ -15,7 +15,7 @@ import { Composite, useCompositeStore } from '..';
import { UseCompositeStorePlaceholder, transform } from './utils';
const meta: Meta< typeof UseCompositeStorePlaceholder > = {
- title: 'Components/Composite',
+ title: 'Components/Composite (V2)',
component: UseCompositeStorePlaceholder,
subcomponents: {
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
@@ -50,6 +50,7 @@ const meta: Meta< typeof UseCompositeStorePlaceholder > = {
options: [ 'horizontal', 'vertical', 'both' ],
},
},
+ tags: [ 'status-private' ],
parameters: {
controls: { expanded: true },
docs: {
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index cd6d2a77db9cb6..6483e34dc222a8 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -62,7 +62,6 @@ export {
CompositeItem as __unstableCompositeItem,
useCompositeState as __unstableUseCompositeState,
} from './composite/legacy';
-export { Composite, useCompositeStore } from './composite';
export { ConfirmDialog as __experimentalConfirmDialog } from './confirm-dialog';
export { default as CustomSelectControl } from './custom-select-control';
export { default as Dashicon } from './dashicon';
From b8d195e3196c82318270f561b958b3da943a03e9 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Thu, 15 Aug 2024 02:48:46 +0900
Subject: [PATCH 084/126] FocalPointPicker: Default to new 40px size (#64456)
* FocalPointPicker: Default to new 40px size
* Simplify prop passing
* Remove unnecessary usage
* Add changelog
---------
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.../src/components/global-styles/background-panel.js | 1 -
packages/block-library/src/cover/edit/inspector-controls.js | 1 -
packages/block-library/src/media-text/edit.js | 1 -
packages/components/CHANGELOG.md | 1 +
packages/components/src/focal-point-picker/controls.tsx | 4 +---
packages/components/src/focal-point-picker/index.tsx | 2 --
packages/components/src/focal-point-picker/types.ts | 4 ++--
7 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js
index 906202206d1b76..14b37bb3007658 100644
--- a/packages/block-editor/src/components/global-styles/background-panel.js
+++ b/packages/block-editor/src/components/global-styles/background-panel.js
@@ -585,7 +585,6 @@ function BackgroundSizeControls( {
return (
{};
export default function FocalPointPickerControls( {
__nextHasNoMarginBottom,
- __next40pxDefaultSize,
hasHelpText,
onChange = noop,
point = {
@@ -57,7 +56,6 @@ export default function FocalPointPickerControls( {
gap={ 4 }
>
{
diff --git a/packages/components/src/focal-point-picker/types.ts b/packages/components/src/focal-point-picker/types.ts
index bd66ae02451a95..357af1e090d7f5 100644
--- a/packages/components/src/focal-point-picker/types.ts
+++ b/packages/components/src/focal-point-picker/types.ts
@@ -29,7 +29,8 @@ export type FocalPointPickerProps = Pick<
/**
* Start opting into the larger default height that will become the default size in a future version.
*
- * @default false
+ * @deprecated Default behavior since WP 6.7. Prop can be safely removed.
+ * @ignore
*/
__next40pxDefaultSize?: boolean;
/**
@@ -68,7 +69,6 @@ export type FocalPointPickerProps = Pick<
export type FocalPointPickerControlsProps = {
__nextHasNoMarginBottom?: boolean;
- __next40pxDefaultSize?: boolean;
/**
* A bit of extra bottom margin will be added if a `help` text
* needs to be rendered under it.
From 1b6c106d704ad2eab5f576d13064f4ee1399ec61 Mon Sep 17 00:00:00 2001
From: Shail Mehta
Date: Thu, 15 Aug 2024 02:14:20 +0530
Subject: [PATCH 085/126] Updated small typo in modularity.md (#64518)
Co-authored-by: shail-mehta
Co-authored-by: ndiego
---
docs/explanations/architecture/modularity.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/explanations/architecture/modularity.md b/docs/explanations/architecture/modularity.md
index f94f8ec7b9472e..ff619ccbfdf5b7 100644
--- a/docs/explanations/architecture/modularity.md
+++ b/docs/explanations/architecture/modularity.md
@@ -42,7 +42,7 @@ function MyApp() {
```php
// myplugin.php
-// Example of script registration dependending on the "components" and "element packages.
+// Example of script registration depending on the "components" and "element packages.
wp_register_script( 'myscript', 'pathtomyscript.js', array ('wp-components', "react" ) );
```
From cf8aeba074334550bc42471b022dac7137aa1eb2 Mon Sep 17 00:00:00 2001
From: Felix Arntz
Date: Wed, 14 Aug 2024 15:05:11 -0700
Subject: [PATCH 086/126] Make wp-env compatible with WordPress versions older
than 5.4 by fixing wp-config anchors (#55864)
* Make wp-env compatible with WordPress versions older than 5.4 by fixing wp-config anchors.
* Use simple version compare function instead of semver library.
* Fix lint violations.
* Convert wpVersion to a string if needed.
* Simplify utility function for WP version check.
* Temp test.
* Temp test.
* Temp test.
* Remove temp code.
* Add missing spaces in WP 5.1-5.3 code.
---
packages/env/lib/wordpress.js | 54 +++++++++++++++++++++++++++++++++--
1 file changed, 52 insertions(+), 2 deletions(-)
diff --git a/packages/env/lib/wordpress.js b/packages/env/lib/wordpress.js
index 423547fad688b5..bd3c4a23f8ff5d 100644
--- a/packages/env/lib/wordpress.js
+++ b/packages/env/lib/wordpress.js
@@ -27,6 +27,30 @@ const { getCache, setCache } = require( './cache' );
* @typedef {'development'|'tests'|'all'} WPEnvironmentSelection
*/
+/**
+ * Utility function to check if a WordPress version is lower than another version.
+ *
+ * This is a non-comprehensive check only intended for this usage, to avoid pulling in a full semver library.
+ * It only considers the major and minor portions of the version and ignores the rest. Additionally, it assumes that
+ * the minor version is always a single digit (i.e. 0-9).
+ *
+ * Do not use this function for general version comparison, as it will not work for all cases.
+ *
+ * @param {string} version The version to check.
+ * @param {string} compareVersion The compare version to check whether the version is lower than.
+ * @return {boolean} True if the version is lower than the compare version, false otherwise.
+ */
+function isWPMajorMinorVersionLower( version, compareVersion ) {
+ const versionNumber = Number.parseFloat(
+ version.match( /^[0-9]+(\.[0-9]+)?/ )[ 0 ]
+ );
+ const compareVersionNumber = Number.parseFloat(
+ compareVersion.match( /^[0-9]+(\.[0-9]+)?/ )[ 0 ]
+ );
+
+ return versionNumber < compareVersionNumber;
+}
+
/**
* Checks a WordPress database connection. An error is thrown if the test is
* unsuccessful.
@@ -51,11 +75,28 @@ async function checkDatabaseConnection( { dockerComposeConfigPath, debug } ) {
* @param {Object} spinner A CLI spinner which indicates progress.
*/
async function configureWordPress( environment, config, spinner ) {
+ let wpVersion = '';
+ try {
+ wpVersion = await readWordPressVersion(
+ config.env[ environment ].coreSource,
+ spinner,
+ config.debug
+ );
+ } catch ( err ) {
+ // Ignore error.
+ }
+
const installCommand = `wp core install --url="${ config.env[ environment ].config.WP_SITEURL }" --title="${ config.name }" --admin_user=admin --admin_password=password --admin_email=wordpress@example.com --skip-email`;
// -eo pipefail exits the command as soon as anything fails in bash.
const setupCommands = [ 'set -eo pipefail', installCommand ];
+ // WordPress versions below 5.1 didn't use proper spacing in wp-config.
+ const configAnchor =
+ wpVersion && isWPMajorMinorVersionLower( wpVersion, '5.1' )
+ ? `"define('WP_DEBUG',"`
+ : `"define( 'WP_DEBUG',"`;
+
// Set wp-config.php values.
for ( let [ key, value ] of Object.entries(
config.env[ environment ].config
@@ -68,7 +109,7 @@ async function configureWordPress( environment, config, spinner ) {
// Add quotes around string values to work with multi-word strings better.
value = typeof value === 'string' ? `"${ value }"` : value;
setupCommands.push(
- `wp config set ${ key } ${ value } --anchor="define( 'WP_DEBUG',"${
+ `wp config set ${ key } ${ value } --anchor=${ configAnchor }${
typeof value !== 'string' ? ' --raw' : ''
}`
);
@@ -98,6 +139,15 @@ async function configureWordPress( environment, config, spinner ) {
}
);
+ // WordPress versions below 5.1 didn't use proper spacing in wp-config.
+ // Additionally, WordPress versions below 5.4 used `dirname( __FILE__ )` instead of `__DIR__`.
+ let abspathDef = `define( 'ABSPATH', __DIR__ . '\\/' );`;
+ if ( wpVersion && isWPMajorMinorVersionLower( wpVersion, '5.1' ) ) {
+ abspathDef = `define('ABSPATH', dirname(__FILE__) . '\\/');`;
+ } else if ( wpVersion && isWPMajorMinorVersionLower( wpVersion, '5.4' ) ) {
+ abspathDef = `define( 'ABSPATH', dirname( __FILE__ ) . '\\/' );`;
+ }
+
// WordPress' PHPUnit suite expects a `wp-tests-config.php` in
// the directory that the test suite is contained within.
// Make sure ABSPATH points to the WordPress install.
@@ -106,7 +156,7 @@ async function configureWordPress( environment, config, spinner ) {
[
'sh',
'-c',
- `sed -e "/^require.*wp-settings.php/d" -e "s/define( 'ABSPATH', __DIR__ . '\\/' );/define( 'ABSPATH', '\\/var\\/www\\/html\\/' );\\n\\tdefine( 'WP_DEFAULT_THEME', 'default' );/" /var/www/html/wp-config.php > /wordpress-phpunit/wp-tests-config.php`,
+ `sed -e "/^require.*wp-settings.php/d" -e "s/${ abspathDef }/define( 'ABSPATH', '\\/var\\/www\\/html\\/' );\\n\\tdefine( 'WP_DEFAULT_THEME', 'default' );/" /var/www/html/wp-config.php > /wordpress-phpunit/wp-tests-config.php`,
],
{
config: config.dockerComposeConfigPath,
From 454129b07628e7fead84f158a4d05580ee5a6d3f Mon Sep 17 00:00:00 2001
From: Ramon
Date: Thu, 15 Aug 2024 10:20:51 +1000
Subject: [PATCH 087/126] `RangeControl`: tweak mark and label absolute
positioning (#64487)
* This commit tweaks the position of the range control marks and labels to fit the original design.
* CHANGELOG.md update
Co-authored-by: ramonjd
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: jameskoster
---
packages/components/CHANGELOG.md | 4 ++++
.../src/range-control/styles/range-control-styles.ts | 4 ++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 2530b66152bdc2..f7b7bdc088cee9 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -28,6 +28,10 @@
- `FocalPointPicker`: Default to new 40px size ([#64456](https://github.com/WordPress/gutenberg/pull/64456)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
+### Bug Fixes
+
+- `RangeControl`: tweak mark and label absolute positioning ([#64487](https://github.com/WordPress/gutenberg/pull/64487)).
+
### Internal
- `Composite` v2: add `Hover` and `Typeahead` subcomponents ([#64399](https://github.com/WordPress/gutenberg/pull/64399)).
diff --git a/packages/components/src/range-control/styles/range-control-styles.ts b/packages/components/src/range-control/styles/range-control-styles.ts
index 89f4864aee2ea6..ec1572d2679247 100644
--- a/packages/components/src/range-control/styles/range-control-styles.ts
+++ b/packages/components/src/range-control/styles/range-control-styles.ts
@@ -154,7 +154,7 @@ export const Mark = styled.span`
height: ${ thumbSize }px;
left: 0;
position: absolute;
- top: -4px;
+ top: 9px;
width: 1px;
${ markFill };
@@ -170,7 +170,7 @@ export const MarkLabel = styled.span`
color: ${ COLORS.gray[ 300 ] };
font-size: 11px;
position: absolute;
- top: 12px;
+ top: 22px;
white-space: nowrap;
${ rtl( { left: 0 } ) };
From add79ad40619242e31a1cc57f38d1aa44ebb9929 Mon Sep 17 00:00:00 2001
From: Ramon
Date: Thu, 15 Aug 2024 12:42:09 +1000
Subject: [PATCH 088/126] Style engine: export util to compile CSS custom var
from preset string (#64490)
Remove `compileStyleValue` in favour of pre-existing `getCSSValueFromRawStyle()` from the style engine package.
Update style engine tests to cover bugfix in https://github.com/WordPress/gutenberg/pull/43116
Moves style engine utils tests into correct directory
Co-authored-by: ramonjd
Co-authored-by: aaronrobertshaw
Co-authored-by: kevin940726
---
.../global-styles/use-global-styles-output.js | 7 +--
.../src/components/global-styles/utils.js | 31 +---------
packages/style-engine/CHANGELOG.md | 4 ++
packages/style-engine/README.md | 16 +++++
packages/style-engine/src/index.ts | 3 +
.../style-engine/src/styles/test/utils.js | 54 ++++++++++++++++
packages/style-engine/src/styles/utils.ts | 18 ++++--
packages/style-engine/src/test/utils.js | 62 -------------------
packages/style-engine/src/types.ts | 2 +-
9 files changed, 96 insertions(+), 101 deletions(-)
create mode 100644 packages/style-engine/src/styles/test/utils.js
delete mode 100644 packages/style-engine/src/test/utils.js
diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js
index c1449d6d8b298f..cd4ad0cea50e0d 100644
--- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js
+++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js
@@ -10,7 +10,7 @@ import {
} from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useContext, useMemo } from '@wordpress/element';
-import { getCSSRules } from '@wordpress/style-engine';
+import { getCSSRules, getCSSValueFromRawStyle } from '@wordpress/style-engine';
import { privateApis as componentsPrivateApis } from '@wordpress/components';
/**
@@ -24,7 +24,6 @@ import {
scopeFeatureSelectors,
appendToSelector,
getBlockStyleVariationSelector,
- compileStyleValue,
getResolvedValue,
} from './utils';
import { getBlockCSSSelector } from './get-block-css-selector';
@@ -357,7 +356,7 @@ export function getStylesDeclarations(
? name
: kebabCase( name );
declarations.push(
- `${ cssProperty }: ${ compileStyleValue(
+ `${ cssProperty }: ${ getCSSValueFromRawStyle(
getValueFromObjectPath( styleValue, [ prop ] )
) }`
);
@@ -369,7 +368,7 @@ export function getStylesDeclarations(
? key
: kebabCase( key );
declarations.push(
- `${ cssProperty }: ${ compileStyleValue(
+ `${ cssProperty }: ${ getCSSValueFromRawStyle(
getValueFromObjectPath( blockStyles, pathToValue )
) }`
);
diff --git a/packages/block-editor/src/components/global-styles/utils.js b/packages/block-editor/src/components/global-styles/utils.js
index 8de479e39382e5..4cd93357b081b3 100644
--- a/packages/block-editor/src/components/global-styles/utils.js
+++ b/packages/block-editor/src/components/global-styles/utils.js
@@ -7,6 +7,7 @@ import fastDeepEqual from 'fast-deep-equal/es6';
* WordPress dependencies
*/
import { useViewportMatch } from '@wordpress/compose';
+import { getCSSValueFromRawStyle } from '@wordpress/style-engine';
/**
* Internal dependencies
@@ -526,34 +527,6 @@ export function getBlockStyleVariationSelector( variation, blockSelector ) {
return result.join( ',' );
}
-/**
- * Converts style preset values `var:` to CSS custom var values.
- * TODO: Export and use the style engine util: getCSSVarFromStyleValue().
- *
- * Example:
- *
- * compileStyleValue( 'var:preset|color|primary' ) // returns 'var(--wp--color-primary)'
- *
- * @param {string} uncompiledValue A block style value.
- * @return {string} The compiled, or original value.
- */
-export function compileStyleValue( uncompiledValue ) {
- const VARIABLE_REFERENCE_PREFIX = 'var:';
- if (
- 'string' === typeof uncompiledValue &&
- uncompiledValue?.startsWith?.( VARIABLE_REFERENCE_PREFIX )
- ) {
- const VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE = '|';
- const VARIABLE_PATH_SEPARATOR_TOKEN_STYLE = '--';
- const variable = uncompiledValue
- .slice( VARIABLE_REFERENCE_PREFIX.length )
- .split( VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE )
- .join( VARIABLE_PATH_SEPARATOR_TOKEN_STYLE );
- return `var(--wp--${ variable })`;
- }
- return uncompiledValue;
-}
-
/**
* Looks up a theme file URI based on a relative path.
*
@@ -591,7 +564,7 @@ export function getResolvedRefValue( ruleValue, tree ) {
if ( typeof ruleValue !== 'string' && ruleValue?.ref ) {
const refPath = ruleValue.ref.split( '.' );
- const resolvedRuleValue = compileStyleValue(
+ const resolvedRuleValue = getCSSValueFromRawStyle(
getValueFromObjectPath( tree, refPath )
);
diff --git a/packages/style-engine/CHANGELOG.md b/packages/style-engine/CHANGELOG.md
index d393e427a63071..42928ac9fbd3c7 100644
--- a/packages/style-engine/CHANGELOG.md
+++ b/packages/style-engine/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### New Features
+
+- Style engine: export util to compile CSS custom var from preset string. ([#64490](https://github.com/WordPress/gutenberg/pull/64490))
+
## 2.5.0 (2024-08-07)
## 2.4.0 (2024-07-24)
diff --git a/packages/style-engine/README.md b/packages/style-engine/README.md
index 44b78eed8a81a6..14e8edbd0dec26 100644
--- a/packages/style-engine/README.md
+++ b/packages/style-engine/README.md
@@ -258,6 +258,22 @@ _Changelog_
`6.1.0` Introduced in WordPress core.
+### getCSSValueFromRawStyle
+
+Returns a WordPress CSS custom var value from incoming style preset value. The preset value follows the pattern `var:description|context|slug`.
+
+Example:
+
+`getCSSValueFromRawStyle( 'var:preset|color|heavenlyBlue' )` // returns 'var(--wp--preset--color--heavenly-blue)'
+
+_Parameters_
+
+- _styleValue_ `string | any`: A raw style value.
+
+_Returns_
+
+- `string | unknown`: A CSS var value.
+
## Glossary
diff --git a/packages/style-engine/src/index.ts b/packages/style-engine/src/index.ts
index fe5b9929877be7..102d5842e7a0be 100644
--- a/packages/style-engine/src/index.ts
+++ b/packages/style-engine/src/index.ts
@@ -91,3 +91,6 @@ export function getCSSRules(
return rules;
}
+
+// Export style utils.
+export { getCSSValueFromRawStyle } from './styles/utils';
diff --git a/packages/style-engine/src/styles/test/utils.js b/packages/style-engine/src/styles/test/utils.js
new file mode 100644
index 00000000000000..02f0adf494e71f
--- /dev/null
+++ b/packages/style-engine/src/styles/test/utils.js
@@ -0,0 +1,54 @@
+/**
+ * Internal dependencies
+ */
+import { camelCaseJoin, getCSSValueFromRawStyle, upperFirst } from '../utils';
+
+describe( 'utils', () => {
+ describe( 'upperFirst()', () => {
+ it( 'should return an string with a capitalized first letter', () => {
+ expect( upperFirst( 'toontown' ) ).toEqual( 'Toontown' );
+ } );
+ } );
+
+ describe( 'camelCaseJoin()', () => {
+ it( 'should return a camelCase string', () => {
+ expect( camelCaseJoin( [ 'toon', 'town' ] ) ).toEqual( 'toonTown' );
+ } );
+ } );
+
+ describe( 'getCSSValueFromRawStyle()', () => {
+ it.each( [
+ [ 'min(40%, 400px)', 'min(40%, 400px)' ],
+ [
+ 'var(--wp--preset--color--yellow-bun)',
+ 'var:preset|color|yellow-bun',
+ ],
+ [ 'var(--wp--preset--font-size--h-1)', 'var:preset|font-size|h1' ],
+ [
+ 'var(--wp--preset--font-size--1-px)',
+ 'var:preset|font-size|1px',
+ ],
+ [
+ 'var(--wp--preset--color--orange-11-orange)',
+ 'var:preset|color|orange11orange',
+ ],
+ [
+ 'var(--wp--preset--color--heavenly-blue)',
+ 'var:preset|color|heavenlyBlue',
+ ],
+ [
+ 'var(--wp--preset--background--dark-secrets-100)',
+ 'var:preset|background|dark_Secrets_100',
+ ],
+ [ null, null ],
+ [ false, false ],
+ [ 1000, 1000 ],
+ [ undefined, undefined ],
+ ] )(
+ 'should return %s using an incoming value of %s',
+ ( expected, value ) => {
+ expect( getCSSValueFromRawStyle( value ) ).toEqual( expected );
+ }
+ );
+ } );
+} );
diff --git a/packages/style-engine/src/styles/utils.ts b/packages/style-engine/src/styles/utils.ts
index 00e9dab8b5892b..0c9b40c384b101 100644
--- a/packages/style-engine/src/styles/utils.ts
+++ b/packages/style-engine/src/styles/utils.ts
@@ -61,7 +61,7 @@ export function generateRule(
{
selector: options?.selector,
key: ruleKey,
- value: getCSSVarFromStyleValue( styleValue ),
+ value: getCSSValueFromRawStyle( styleValue ),
},
]
: [];
@@ -103,7 +103,7 @@ export function generateBoxRules(
} else {
const sideRules = individualProperties.reduce(
( acc: GeneratedCSSRule[], side: string ) => {
- const value: string | undefined = getCSSVarFromStyleValue(
+ const value = getCSSValueFromRawStyle(
getStyleValueByPath( boxStyle, [ side ] )
);
if ( value ) {
@@ -127,13 +127,21 @@ export function generateBoxRules(
}
/**
- * Returns a CSS var value from incoming style value following the pattern `var:description|context|slug`.
+ * Returns a WordPress CSS custom var value from incoming style preset value.
+ * The preset value follows the pattern `var:description|context|slug`.
+ *
+ * Example:
+ *
+ * `getCSSValueFromRawStyle( 'var:preset|color|heavenlyBlue' )` // returns 'var(--wp--preset--color--heavenly-blue)'
*
* @param styleValue A raw style value.
*
- * @return string A CSS var value.
+ * @return A CSS var value.
*/
-export function getCSSVarFromStyleValue( styleValue: string ): string {
+
+export function getCSSValueFromRawStyle(
+ styleValue: string | any
+): string | unknown {
if (
typeof styleValue === 'string' &&
styleValue.startsWith( VARIABLE_REFERENCE_PREFIX )
diff --git a/packages/style-engine/src/test/utils.js b/packages/style-engine/src/test/utils.js
deleted file mode 100644
index 9f1f84d2b45310..00000000000000
--- a/packages/style-engine/src/test/utils.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Internal dependencies
- */
-import {
- camelCaseJoin,
- getCSSVarFromStyleValue,
- upperFirst,
-} from '../styles/utils';
-
-describe( 'utils', () => {
- describe( 'upperFirst()', () => {
- it( 'should return an string with a capitalized first letter', () => {
- expect( upperFirst( 'toontown' ) ).toEqual( 'Toontown' );
- } );
- } );
-
- describe( 'camelCaseJoin()', () => {
- it( 'should return a camelCase string', () => {
- expect( camelCaseJoin( [ 'toon', 'town' ] ) ).toEqual( 'toonTown' );
- } );
- } );
-
- describe( 'getCSSVarFromStyleValue()', () => {
- it( 'should return a compiled CSS var', () => {
- expect(
- getCSSVarFromStyleValue( 'var:preset|color|yellow-bun' )
- ).toEqual( 'var(--wp--preset--color--yellow-bun)' );
- } );
-
- it( 'should kebab case numbers', () => {
- expect(
- getCSSVarFromStyleValue( 'var:preset|font-size|h1' )
- ).toEqual( 'var(--wp--preset--font-size--h-1)' );
- } );
-
- it( 'should kebab case numbers as prefix', () => {
- expect(
- getCSSVarFromStyleValue( 'var:preset|font-size|1px' )
- ).toEqual( 'var(--wp--preset--font-size--1-px)' );
- } );
-
- it( 'should kebab case both sides of numbers', () => {
- expect(
- getCSSVarFromStyleValue( 'var:preset|color|orange11orange' )
- ).toEqual( 'var(--wp--preset--color--orange-11-orange)' );
- } );
-
- it( 'should kebab case camel case', () => {
- expect(
- getCSSVarFromStyleValue( 'var:preset|color|heavenlyBlue' )
- ).toEqual( 'var(--wp--preset--color--heavenly-blue)' );
- } );
-
- it( 'should kebab case underscores', () => {
- expect(
- getCSSVarFromStyleValue(
- 'var:preset|background|dark_Secrets_100'
- )
- ).toEqual( 'var(--wp--preset--background--dark-secrets-100)' );
- } );
- } );
-} );
diff --git a/packages/style-engine/src/types.ts b/packages/style-engine/src/types.ts
index 5b361836a8e375..8de623fcfcb2bf 100644
--- a/packages/style-engine/src/types.ts
+++ b/packages/style-engine/src/types.ts
@@ -94,7 +94,7 @@ export interface StyleOptions {
export interface GeneratedCSSRule {
selector?: string;
- value: string;
+ value: string | unknown;
/**
* The CSS key in JS style attribute format, compatible with React.
* E.g. `paddingTop` instead of `padding-top`.
From de656233be92e37fd1a7fdb0e31cf87824f63776 Mon Sep 17 00:00:00 2001
From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>
Date: Thu, 15 Aug 2024 10:18:30 +0700
Subject: [PATCH 089/126] List Item: Add border support (#63541)
Co-authored-by: aaronrobertshaw
Co-authored-by: andrewserong
---
packages/block-library/src/list-item/block.json | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/packages/block-library/src/list-item/block.json b/packages/block-library/src/list-item/block.json
index fc7117d1b792fc..a4bf2351d97509 100644
--- a/packages/block-library/src/list-item/block.json
+++ b/packages/block-library/src/list-item/block.json
@@ -23,6 +23,12 @@
"anchor": true,
"className": false,
"splitting": true,
+ "__experimentalBorder": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true
+ },
"color": {
"gradients": true,
"link": true,
@@ -57,6 +63,7 @@
}
},
"selectors": {
- "root": ".wp-block-list > li"
+ "root": ".wp-block-list > li",
+ "border": ".wp-block-list:not(.wp-block-list .wp-block-list) > li"
}
}
From e6057ce69fb2f743fb0c8a71494f56ff39f5a900 Mon Sep 17 00:00:00 2001
From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>
Date: Thu, 15 Aug 2024 10:19:07 +0700
Subject: [PATCH 090/126] List: Add border support (#63540)
Co-authored-by: aaronrobertshaw
Co-authored-by: andrewserong
Co-authored-by: carolinan
---
packages/block-library/src/list/block.json | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/packages/block-library/src/list/block.json b/packages/block-library/src/list/block.json
index 8b100071c15ea9..ea07a0eb542df3 100644
--- a/packages/block-library/src/list/block.json
+++ b/packages/block-library/src/list/block.json
@@ -39,6 +39,12 @@
"supports": {
"anchor": true,
"html": false,
+ "__experimentalBorder": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true
+ },
"typography": {
"fontSize": true,
"lineHeight": true,
@@ -75,6 +81,9 @@
"clientNavigation": true
}
},
+ "selectors": {
+ "border": ".wp-block-list:not(.wp-block-list .wp-block-list)"
+ },
"editorStyle": "wp-block-list-editor",
"style": "wp-block-list"
}
From d669eb661c69af31816ead9359c5986cffcea301 Mon Sep 17 00:00:00 2001
From: Ramon
Date: Thu, 15 Aug 2024 13:44:09 +1000
Subject: [PATCH 091/126] Background images: add support for theme.json ref
value resolution (#64128)
Resolves refs before passing styles to style engine in the WP_Theme_JSON class. Consolidates all resolutions in the background panel (image and ref). Removes useGlobalStyleLinks completely. Delete theme URI utils.
Ensures that backgroundImage style objects are not merged, but replaced when merged theme.json configs. For example, an incoming config such as a saved user config should overwrite the background styles of the base theme.json, where it exists.
---------
Co-authored-by: ramonjd
Co-authored-by: aaronrobertshaw
Co-authored-by: andrewserong
Co-authored-by: kevin940726
---
backport-changelog/6.7/7137.md | 1 +
lib/class-wp-theme-json-gutenberg.php | 68 +++++---
.../global-styles/background-panel.js | 74 +++++---
.../src/components/global-styles/hooks.js | 5 -
.../src/components/global-styles/index.js | 1 -
.../test/theme-file-uri-utils.js | 41 -----
.../test/use-global-styles-output.js | 16 ++
.../global-styles/theme-file-uri-utils.js | 18 --
.../src/components/global-styles/utils.js | 5 +
packages/block-editor/src/hooks/background.js | 9 +-
.../global-styles/background-panel.js | 3 -
.../components/global-styles/screen-block.js | 3 -
.../global-styles-provider/index.js | 19 ++-
.../src/styles/background/index.ts | 26 ++-
phpunit/class-wp-theme-json-test.php | 160 ++++++++++++++++++
15 files changed, 313 insertions(+), 136 deletions(-)
delete mode 100644 packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js
delete mode 100644 packages/block-editor/src/components/global-styles/theme-file-uri-utils.js
diff --git a/backport-changelog/6.7/7137.md b/backport-changelog/6.7/7137.md
index 1eba52ebaf5087..00771b8bc6c21d 100644
--- a/backport-changelog/6.7/7137.md
+++ b/backport-changelog/6.7/7137.md
@@ -1,4 +1,5 @@
https://github.com/WordPress/wordpress-develop/pull/7137
+* https://github.com/WordPress/gutenberg/pull/64128
* https://github.com/WordPress/gutenberg/pull/64192
* https://github.com/WordPress/gutenberg/pull/64328
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 20ea31090407b4..756ef06c80aa87 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -2329,7 +2329,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
* ```php
* array(
* 'name' => 'property_name',
- * 'value' => 'property_value,
+ * 'value' => 'property_value',
* )
* ```
*
@@ -2338,6 +2338,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
* @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters.
* @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
* @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). Using style engine to correctly fetch background CSS values.
+ * @since 6.7.0 Allow ref resolution of background properties.
*
* @param array $styles Styles to process.
* @param array $settings Theme settings.
@@ -2381,21 +2382,28 @@ protected static function compute_style_properties( $styles, $settings = array()
$root_variable_duplicates[] = substr( $css_property, $root_style_length );
}
- // Processes background styles.
- if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) {
- /*
- * For user-uploaded images at the block level, assign defaults.
- * Matches defaults applied in the editor and in block supports: background.php.
- */
- if ( static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
- $styles['background']['backgroundSize'] = $styles['background']['backgroundSize'] ?? 'cover';
- // If the background size is set to `contain` and no position is set, set the position to `center`.
- if ( 'contain' === $styles['background']['backgroundSize'] && empty( $styles['background']['backgroundPosition'] ) ) {
- $styles['background']['backgroundPosition'] = '50% 50%';
- }
+ /*
+ * Processes background image styles.
+ * If the value is a URL, it will be converted to a CSS `url()` value.
+ * For an uploaded image (images with a database ID), apply size and position
+ * defaults equal to those applied in block supports in lib/background.php.
+ */
+ if ( 'background-image' === $css_property && ! empty( $value ) ) {
+ $background_styles = gutenberg_style_engine_get_styles(
+ array( 'background' => array( 'backgroundImage' => $value ) )
+ );
+
+ $value = $background_styles['declarations'][ $css_property ];
+ }
+ if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
+ if ( 'background-size' === $css_property ) {
+ $value = 'cover';
+ }
+ // If the background size is set to `contain` and no position is set, set the position to `center`.
+ if ( 'background-position' === $css_property ) {
+ $background_size = $styles['background']['backgroundSize'] ?? null;
+ $value = 'contain' === $background_size ? '50% 50%' : null;
}
- $background_styles = gutenberg_style_engine_get_styles( array( 'background' => $styles['background'] ) );
- $value = $background_styles['declarations'][ $css_property ] ?? $value;
}
// Skip if empty and not "0" or value represents array of longhand values.
@@ -2463,6 +2471,7 @@ protected static function compute_style_properties( $styles, $settings = array()
* @since 5.8.0
* @since 5.9.0 Added support for values of array type, which are returned as is.
* @since 6.1.0 Added the `$theme_json` parameter.
+ * @since 6.7.0 Added support for background image refs
*
* @param array $styles Styles subtree.
* @param array $path Which property to process.
@@ -2479,15 +2488,17 @@ protected static function get_property_value( $styles, $path, $theme_json = null
}
/*
- * This converts references to a path to the value at that path
- * where the values is an array with a "ref" key, pointing to a path.
+ * Where the current value is an array with a 'ref' key pointing
+ * to a path, this converts that path into the value at that path.
* For example: { "ref": "style.color.background" } => "#fff".
*/
if ( is_array( $value ) && isset( $value['ref'] ) ) {
$value_path = explode( '.', $value['ref'] );
- $ref_value = _wp_array_get( $theme_json, $value_path );
+ $ref_value = _wp_array_get( $theme_json, $value_path, null );
+ // Background Image refs can refer to a string or an array containing a URL string.
+ $ref_value_url = $ref_value['url'] ?? null;
// Only use the ref value if we find anything.
- if ( ! empty( $ref_value ) && is_string( $ref_value ) ) {
+ if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) {
$value = $ref_value;
}
@@ -3247,6 +3258,25 @@ public function merge( $incoming ) {
}
}
}
+
+ /*
+ * Style values are merged at the leaf level, however
+ * some values provide exceptions, namely style values that are
+ * objects and represent unique definitions for the style.
+ */
+ $style_nodes = static::get_styles_block_nodes();
+ foreach ( $style_nodes as $style_node ) {
+ $path = $style_node['path'];
+ /*
+ * Background image styles should be replaced, not merged,
+ * as they themselves are specific object definitions for the style.
+ */
+ $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] );
+ $content = _wp_array_get( $incoming_data, $background_image_path, null );
+ if ( isset( $content ) ) {
+ _wp_array_set( $this->theme_json, $background_image_path, $content );
+ }
+ }
}
/**
diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js
index 14b37bb3007658..1373c54764d155 100644
--- a/packages/block-editor/src/components/global-styles/background-panel.js
+++ b/packages/block-editor/src/components/global-styles/background-panel.js
@@ -34,6 +34,7 @@ import {
useRef,
useState,
useEffect,
+ useMemo,
} from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { focus } from '@wordpress/dom';
@@ -42,11 +43,15 @@ import { isBlobURL } from '@wordpress/blob';
/**
* Internal dependencies
*/
-import { useToolsPanelDropdownMenuProps } from './utils';
+import { useToolsPanelDropdownMenuProps, getResolvedValue } from './utils';
import { setImmutably } from '../../utils/object';
import MediaReplaceFlow from '../media-replace-flow';
import { store as blockEditorStore } from '../../store';
-import { getResolvedThemeFilePath } from './theme-file-uri-utils';
+
+import {
+ globalStylesDataKey,
+ globalStylesLinksDataKey,
+} from '../../store/private-keys';
const IMAGE_BACKGROUND_TYPE = 'image';
const DEFAULT_CONTROLS = {
@@ -270,7 +275,6 @@ function BackgroundImageControls( {
onRemoveImage = noop,
onResetImage = noop,
displayInPanel,
- themeFileURIs,
defaultValues,
} ) {
const mediaUpload = useSelect(
@@ -404,10 +408,7 @@ function BackgroundImageControls( {
name={
@@ -449,7 +450,6 @@ function BackgroundSizeControls( {
style,
inheritedValue,
defaultValues,
- themeFileURIs,
} ) {
const sizeValue =
style?.background?.backgroundSize ||
@@ -587,7 +587,7 @@ function BackgroundSizeControls( {
@@ -697,8 +697,44 @@ export default function BackgroundPanel( {
defaultControls = DEFAULT_CONTROLS,
defaultValues = {},
headerLabel = __( 'Background image' ),
- themeFileURIs,
} ) {
+ /*
+ * Resolve any inherited "ref" pointers.
+ * Should the block editor need resolved, inherited values
+ * across all controls, this could be abstracted into a hook,
+ * e.g., useResolveGlobalStyle
+ */
+ const { globalStyles, _links } = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ const _settings = getSettings();
+ return {
+ globalStyles: _settings[ globalStylesDataKey ],
+ _links: _settings[ globalStylesLinksDataKey ],
+ };
+ }, [] );
+ const resolvedInheritedValue = useMemo( () => {
+ const resolvedValues = {
+ background: {},
+ };
+
+ if ( ! inheritedValue?.background ) {
+ return inheritedValue;
+ }
+
+ Object.entries( inheritedValue?.background ).forEach(
+ ( [ key, backgroundValue ] ) => {
+ resolvedValues.background[ key ] = getResolvedValue(
+ backgroundValue,
+ {
+ styles: globalStyles,
+ _links,
+ }
+ );
+ }
+ );
+ return resolvedValues;
+ }, [ globalStyles, _links, inheritedValue ] );
+
const resetAllFilter = useCallback( ( previousValue ) => {
return {
...previousValue,
@@ -710,11 +746,11 @@ export default function BackgroundPanel( {
onChange( setImmutably( value, [ 'background' ], {} ) );
const { title, url } = value?.background?.backgroundImage || {
- ...inheritedValue?.background?.backgroundImage,
+ ...resolvedInheritedValue?.background?.backgroundImage,
};
const hasImageValue =
hasBackgroundImageValue( value ) ||
- hasBackgroundImageValue( inheritedValue );
+ hasBackgroundImageValue( resolvedInheritedValue );
const imageValue =
value?.background?.backgroundImage ||
@@ -756,10 +792,7 @@ export default function BackgroundPanel( {
@@ -767,8 +800,7 @@ export default function BackgroundPanel( {
{
setIsDropDownOpen( false );
@@ -784,8 +816,7 @@ export default function BackgroundPanel( {
panelId={ panelId }
style={ value }
defaultValues={ defaultValues }
- inheritedValue={ inheritedValue }
- themeFileURIs={ themeFileURIs }
+ inheritedValue={ resolvedInheritedValue }
/>
@@ -793,8 +824,7 @@ export default function BackgroundPanel( {
{
setIsDropDownOpen( false );
diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js
index a1a4fc1a0a6ae1..2be77aec18a2c6 100644
--- a/packages/block-editor/src/components/global-styles/hooks.js
+++ b/packages/block-editor/src/components/global-styles/hooks.js
@@ -209,11 +209,6 @@ export function useGlobalStyle(
return [ result, setStyle ];
}
-export function useGlobalStyleLinks() {
- const { merged: mergedConfig } = useContext( GlobalStylesContext );
- return mergedConfig?._links;
-}
-
/**
* React hook that overrides a global settings object with block and element specific settings.
*
diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js
index 062df0a5606e90..8096a48569f199 100644
--- a/packages/block-editor/src/components/global-styles/index.js
+++ b/packages/block-editor/src/components/global-styles/index.js
@@ -3,7 +3,6 @@ export {
useGlobalSetting,
useGlobalStyle,
useSettingsForBlockElement,
- useGlobalStyleLinks,
} from './hooks';
export { getBlockCSSSelector } from './get-block-css-selector';
export {
diff --git a/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js b/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js
deleted file mode 100644
index e239bb0941ea9e..00000000000000
--- a/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Internal dependencies
- */
-import { getResolvedThemeFilePath } from '../theme-file-uri-utils';
-
-const themeFileURIs = [
- {
- name: 'file:./assets/image.jpg',
- href: 'https://wordpress.org/assets/image.jpg',
- target: 'styles.background.backgroundImage.url',
- },
- {
- name: 'file:./assets/other/image.jpg',
- href: 'https://wordpress.org/assets/other/image.jpg',
- target: "styles.blocks.['core/group].background.backgroundImage.url",
- },
-];
-
-describe( 'getResolvedThemeFilePath()', () => {
- it.each( [
- [
- 'file:./assets/image.jpg',
- 'https://wordpress.org/assets/image.jpg',
- 'Should return absolute URL if found in themeFileURIs',
- ],
- [
- 'file:./misc/image.jpg',
- 'file:./misc/image.jpg',
- 'Should return value if not found in themeFileURIs',
- ],
- [
- 'https://wordpress.org/assets/image.jpg',
- 'https://wordpress.org/assets/image.jpg',
- 'Should not match absolute URLs',
- ],
- ] )( 'Given file %s and return value %s: %s', ( file, returnedValue ) => {
- expect(
- getResolvedThemeFilePath( file, themeFileURIs ) === returnedValue
- ).toBe( true );
- } );
-} );
diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
index 1b061f6921f2c2..5022e8ba591dbb 100644
--- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
+++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js
@@ -1008,9 +1008,23 @@ describe( 'global styles renderer', () => {
ref: 'styles.elements.h1.typography.letterSpacing',
},
},
+ background: {
+ backgroundImage: {
+ ref: 'styles.background.backgroundImage',
+ },
+ backgroundSize: {
+ ref: 'styles.background.backgroundSize',
+ },
+ },
};
const tree = {
styles: {
+ background: {
+ backgroundImage: {
+ url: 'http://my-image.org/image.gif',
+ },
+ backgroundSize: 'cover',
+ },
elements: {
h1: {
typography: {
@@ -1026,6 +1040,8 @@ describe( 'global styles renderer', () => {
).toEqual( [
'font-size: var(--wp--preset--font-size--xx-large)',
'letter-spacing: 2px',
+ "background-image: url( 'http://my-image.org/image.gif' )",
+ 'background-size: cover',
] );
} );
it( 'should set default values for block background styles', () => {
diff --git a/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js b/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js
deleted file mode 100644
index 96b3e2e4cb68b0..00000000000000
--- a/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Looks up a theme file URI based on a relative path.
- *
- * @param {string} file A relative path.
- * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths.
- * @return {string?} A resolved theme file URI, if one is found in the themeFileURIs collection.
- */
-export function getResolvedThemeFilePath( file, themeFileURIs = [] ) {
- const uri = themeFileURIs.find(
- ( themeFileUri ) => themeFileUri.name === file
- );
-
- if ( ! uri?.href ) {
- return file;
- }
-
- return uri?.href;
-}
diff --git a/packages/block-editor/src/components/global-styles/utils.js b/packages/block-editor/src/components/global-styles/utils.js
index 4cd93357b081b3..59799c9032c67f 100644
--- a/packages/block-editor/src/components/global-styles/utils.js
+++ b/packages/block-editor/src/components/global-styles/utils.js
@@ -562,6 +562,11 @@ export function getResolvedRefValue( ruleValue, tree ) {
return ruleValue;
}
+ /*
+ * Where the rule value is an object with a 'ref' property pointing
+ * to a path, this converts that path into the value at that path.
+ * For example: { "ref": "style.color.background" } => "#fff".
+ */
if ( typeof ruleValue !== 'string' && ruleValue?.ref ) {
const refPath = ruleValue.ref.split( '.' );
const resolvedRuleValue = getCSSValueFromRawStyle(
diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js
index 0d38068cdefeea..3755aecbcb9d0b 100644
--- a/packages/block-editor/src/hooks/background.js
+++ b/packages/block-editor/src/hooks/background.js
@@ -16,10 +16,7 @@ import {
useHasBackgroundPanel,
hasBackgroundImageValue,
} from '../components/global-styles/background-panel';
-import {
- globalStylesDataKey,
- globalStylesLinksDataKey,
-} from '../store/private-keys';
+import { globalStylesDataKey } from '../store/private-keys';
export const BACKGROUND_SUPPORT_KEY = 'background';
@@ -136,14 +133,13 @@ export function BackgroundImagePanel( {
setAttributes,
settings,
} ) {
- const { style, inheritedValue, _links } = useSelect(
+ const { style, inheritedValue } = useSelect(
( select ) => {
const { getBlockAttributes, getSettings } =
select( blockEditorStore );
const _settings = getSettings();
return {
style: getBlockAttributes( clientId )?.style,
- _links: _settings[ globalStylesLinksDataKey ],
/*
* To ensure we pass down the right inherited values:
* @TODO 1. Pass inherited value down to all block style controls,
@@ -190,7 +186,6 @@ export function BackgroundImagePanel( {
settings={ updatedSettings }
onChange={ onChange }
value={ style }
- themeFileURIs={ _links?.[ 'wp:theme-file' ] }
/>
);
}
diff --git a/packages/edit-site/src/components/global-styles/background-panel.js b/packages/edit-site/src/components/global-styles/background-panel.js
index 24ab914bed8c52..e185079d8cee04 100644
--- a/packages/edit-site/src/components/global-styles/background-panel.js
+++ b/packages/edit-site/src/components/global-styles/background-panel.js
@@ -16,7 +16,6 @@ const BACKGROUND_DEFAULT_VALUES = {
const {
useGlobalStyle,
useGlobalSetting,
- useGlobalStyleLinks,
BackgroundPanel: StylesBackgroundPanel,
} = unlock( blockEditorPrivateApis );
@@ -42,7 +41,6 @@ export default function BackgroundPanel() {
const [ inheritedStyle, setStyle ] = useGlobalStyle( '', undefined, 'all', {
shouldDecodeEncode: false,
} );
- const _links = useGlobalStyleLinks();
const [ settings ] = useGlobalSetting( '' );
return (
@@ -52,7 +50,6 @@ export default function BackgroundPanel() {
onChange={ setStyle }
settings={ settings }
defaultValues={ BACKGROUND_DEFAULT_VALUES }
- themeFileURIs={ _links?.[ 'wp:theme-file' ] }
/>
);
}
diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js
index dee921f37f1e8a..b1489167f2dc75 100644
--- a/packages/edit-site/src/components/global-styles/screen-block.js
+++ b/packages/edit-site/src/components/global-styles/screen-block.js
@@ -85,7 +85,6 @@ const {
FiltersPanel: StylesFiltersPanel,
ImageSettingsPanel,
AdvancedPanel: StylesAdvancedPanel,
- useGlobalStyleLinks,
} = unlock( blockEditorPrivateApis );
function ScreenBlock( { name, variation } ) {
@@ -105,7 +104,6 @@ function ScreenBlock( { name, variation } ) {
const [ rawSettings, setSettings ] = useGlobalSetting( '', name );
const settings = useSettingsForBlockElement( rawSettings, name );
const blockType = getBlockType( name );
- const _links = useGlobalStyleLinks();
// Only allow `blockGap` support if serialization has not been skipped, to be sure global spacing can be applied.
if (
@@ -272,7 +270,6 @@ function ScreenBlock( { name, variation } ) {
onChange={ setStyle }
settings={ settings }
defaultValues={ BACKGROUND_BLOCK_DEFAULT_VALUES }
- themeFileURIs={ _links?.[ 'wp:theme-file' ] }
/>
) }
{ hasTypographyPanel && (
diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js
index 8ac292fb2ce0b2..8426593d8f5f51 100644
--- a/packages/editor/src/components/global-styles-provider/index.js
+++ b/packages/editor/src/components/global-styles-provider/index.js
@@ -23,10 +23,23 @@ const { GlobalStylesContext, cleanEmptyObject } = unlock(
export function mergeBaseAndUserConfigs( base, user ) {
return deepmerge( base, user, {
- // We only pass as arrays the presets,
- // in which case we want the new array of values
- // to override the old array (no merging).
+ /*
+ * We only pass as arrays the presets,
+ * in which case we want the new array of values
+ * to override the old array (no merging).
+ */
isMergeableObject: isPlainObject,
+ /*
+ * Exceptions to the above rule.
+ * Background images should be replaced, not merged,
+ * as they themselves are specific object definitions for the style.
+ */
+ customMerge: ( key ) => {
+ if ( key === 'backgroundImage' ) {
+ return ( baseConfig, userConfig ) => userConfig;
+ }
+ return undefined;
+ },
} );
}
diff --git a/packages/style-engine/src/styles/background/index.ts b/packages/style-engine/src/styles/background/index.ts
index 211b97343d89cc..b943032f9c4417 100644
--- a/packages/style-engine/src/styles/background/index.ts
+++ b/packages/style-engine/src/styles/background/index.ts
@@ -8,6 +8,12 @@ const backgroundImage = {
name: 'backgroundImage',
generate: ( style: Style, options: StyleOptions ) => {
const _backgroundImage = style?.background?.backgroundImage;
+
+ /*
+ * The background image can be a string or an object.
+ * If the background image is a string, it could already contain a url() function,
+ * or have a linear-gradient value.
+ */
if ( typeof _backgroundImage === 'object' && _backgroundImage?.url ) {
return [
{
@@ -21,20 +27,12 @@ const backgroundImage = {
];
}
- /*
- * If the background image is a string, it could already contain a url() function,
- * or have a linear-gradient value.
- */
- if ( typeof _backgroundImage === 'string' ) {
- return generateRule(
- style,
- options,
- [ 'background', 'backgroundImage' ],
- 'backgroundImage'
- );
- }
-
- return [];
+ return generateRule(
+ style,
+ options,
+ [ 'background', 'backgroundImage' ],
+ 'backgroundImage'
+ );
},
};
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index 3f11dd97a6688f..10bb47b87fba89 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -2294,6 +2294,112 @@ public function test_merge_incoming_data_presets_use_default_names() {
$this->assertSameSetsWithIndex( $expected, $actual );
}
+ public function test_merge_incoming_background_styles() {
+ $theme_json = new WP_Theme_JSON_Gutenberg(
+ array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/quote.png',
+ ),
+ 'backgroundSize' => 'cover',
+ ),
+ 'blocks' => array(
+ 'core/group' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'ref' => 'styles.blocks.core/verse.background.backgroundImage',
+ ),
+ 'backgroundAttachment' => 'fixed',
+ ),
+ ),
+ 'core/quote' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/quote.png',
+ ),
+ 'backgroundAttachment' => array(
+ 'ref' => 'styles.blocks.core/group.background.backgroundAttachment',
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ );
+
+ $update_background_image_styles = array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'background' => array(
+ 'backgroundSize' => 'contain',
+ ),
+ 'blocks' => array(
+ 'core/group' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/group.png',
+ ),
+ ),
+ ),
+ 'core/quote' => array(
+ 'background' => array(
+ 'backgroundAttachment' => 'fixed',
+ ),
+ ),
+ 'core/verse' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'ref' => 'styles.blocks.core/group.background.backgroundImage',
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ $expected = array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/quote.png',
+ ),
+ 'backgroundSize' => 'contain',
+ ),
+ 'blocks' => array(
+ 'core/group' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/group.png',
+ ),
+ 'backgroundAttachment' => 'fixed',
+ ),
+ ),
+ 'core/quote' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/quote.png',
+ ),
+ 'backgroundAttachment' => 'fixed',
+ ),
+ ),
+ 'core/verse' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'ref' => 'styles.blocks.core/group.background.backgroundImage',
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ $theme_json->merge( new WP_Theme_JSON_Gutenberg( $update_background_image_styles ) );
+ $actual = $theme_json->get_raw_data();
+
+ $this->assertEqualSetsWithIndex( $expected, $actual );
+ }
+
public function test_remove_insecure_properties_removes_unsafe_styles() {
$actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties(
array(
@@ -4915,6 +5021,60 @@ public function test_get_block_background_image_styles() {
$this->assertSameCSS( $verse_styles, $theme_json->get_styles_for_block( $verse_node ), 'Styles returned from "::get_styles_for_block()" with default core/verse background styles as string type do not match expectations' );
}
+ /**
+ * Testing background dynamic properties in theme.json.
+ */
+ public function test_get_resolved_background_image_styles() {
+ $theme_json = new WP_Theme_JSON_Gutenberg(
+ array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'url' => 'http://example.org/top.png',
+ ),
+ 'backgroundSize' => 'contain',
+ 'backgroundRepeat' => 'repeat',
+ 'backgroundPosition' => '10% 20%',
+ 'backgroundAttachment' => 'scroll',
+ ),
+ 'blocks' => array(
+ 'core/group' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'id' => 123,
+ 'url' => 'http://example.org/group.png',
+ ),
+ ),
+ ),
+ 'core/post-content' => array(
+ 'background' => array(
+ 'backgroundImage' => array(
+ 'ref' => 'styles.background.backgroundImage',
+ ),
+ 'backgroundSize' => array(
+ 'ref' => 'styles.background.backgroundSize',
+ ),
+ 'backgroundRepeat' => array(
+ 'ref' => 'styles.background.backgroundRepeat',
+ ),
+ 'backgroundPosition' => array(
+ 'ref' => 'styles.background.backgroundPosition',
+ ),
+ 'backgroundAttachment' => array(
+ 'ref' => 'styles.background.backgroundAttachment',
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ );
+
+ $expected = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}body{background-image: url('http://example.org/top.png');background-position: 10% 20%;background-repeat: repeat;background-size: contain;background-attachment: scroll;}:root :where(.wp-block-group){background-image: url('http://example.org/group.png');background-size: cover;}:root :where(.wp-block-post-content){background-image: url('http://example.org/top.png');background-position: 10% 20%;background-repeat: repeat;background-size: contain;background-attachment: scroll;}";
+ $this->assertSameCSS( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) );
+ }
+
/**
* Tests that base custom CSS is generated correctly.
*/
From c8eaf5c0c4f4d0bc07fb39626ae3268bd83fbcfe Mon Sep 17 00:00:00 2001
From: Daniel Richards
Date: Thu, 15 Aug 2024 11:49:14 +0800
Subject: [PATCH 092/126] Components: Allow `style` prop on `Popover` (#64489)
* Allow style prop on popover
* Ensure styles are not overwritten by popoverProps
* Add tests and fix overriding of built-in popover styles
* Update comment
* Remove blank line
* Un-should tests
* Update test to use `not.toHaveStyle
* Add changelog entry
* Remove `style` from types file
----
Co-authored-by: talldan
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
packages/components/CHANGELOG.md | 2 ++
packages/components/src/popover/index.tsx | 12 +++++--
.../components/src/popover/test/index.tsx | 34 +++++++++++++++++++
3 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index f7b7bdc088cee9..e758e457bdd03e 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -27,6 +27,8 @@
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
- `FocalPointPicker`: Default to new 40px size ([#64456](https://github.com/WordPress/gutenberg/pull/64456)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
+- `Popover`: allow `style` prop usage ([#64489](https://github.com/WordPress/gutenberg/pull/64489)).
+
### Bug Fixes
diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx
index 68d331ce8266ba..3005f13c952ec0 100644
--- a/packages/components/src/popover/index.tsx
+++ b/packages/components/src/popover/index.tsx
@@ -113,8 +113,9 @@ const UnforwardedPopover = (
WordPressComponentProps< PopoverProps, 'div', false >,
// To avoid overlaps between the standard HTML attributes and the props
// expected by `framer-motion`, omit all framer motion props from popover
- // props (except for `animate` and `children`, which are re-defined in `PopoverProps`).
- keyof Omit< MotionProps, 'animate' | 'children' >
+ // props (except for `animate` and `children` which are re-defined in
+ // `PopoverProps`, and `style` which is merged safely).
+ keyof Omit< MotionProps, 'animate' | 'children' | 'style' >
>,
forwardedRef: ForwardedRef< any >
) => {
@@ -139,6 +140,7 @@ const UnforwardedPopover = (
shift = false,
inline = false,
variant,
+ style: contentStyle,
// Deprecated props
__unstableForcePosition,
@@ -370,6 +372,7 @@ const UnforwardedPopover = (
const animationProps: HTMLMotionProps< 'div' > = shouldAnimate
? {
style: {
+ ...contentStyle,
...motionInlineStyles,
...style,
},
@@ -378,7 +381,10 @@ const UnforwardedPopover = (
}
: {
animate: false,
- style,
+ style: {
+ ...contentStyle,
+ ...style,
+ },
};
// When Floating UI has finished positioning and Framer Motion has finished animating
diff --git a/packages/components/src/popover/test/index.tsx b/packages/components/src/popover/test/index.tsx
index f5ee7e96e4c54d..eac0f942df2f6e 100644
--- a/packages/components/src/popover/test/index.tsx
+++ b/packages/components/src/popover/test/index.tsx
@@ -179,6 +179,40 @@ describe( 'Popover', () => {
} );
} );
+ describe( 'style', () => {
+ it( 'outputs inline styles added through the style prop in addition to built-in popover positioning styles', async () => {
+ render(
+
+ Hello
+
+ );
+ const popover = screen.getByTestId( 'popover-element' );
+
+ await waitFor( () => expect( popover ).toBeVisible() );
+ expect( popover ).toHaveStyle(
+ 'position: absolute; top: 0px; left: 0px; z-index: 0;'
+ );
+ } );
+
+ it( 'is not possible to override built-in popover positioning styles via the style prop', async () => {
+ render(
+
+ Hello
+
+ );
+ const popover = screen.getByTestId( 'popover-element' );
+
+ await waitFor( () => expect( popover ).toBeVisible() );
+ expect( popover ).not.toHaveStyle( 'position: static;' );
+ } );
+ } );
+
describe( 'focus behavior', () => {
it( 'should focus the popover container when opened', async () => {
render(
From 9a9186cd09424a91e3cf59288e2290d6d6867f73 Mon Sep 17 00:00:00 2001
From: Shail Mehta
Date: Thu, 15 Aug 2024 10:25:18 +0530
Subject: [PATCH 093/126] Comment Content: Add Border Support (#64230)
* Commtent Content: Add Border Support
* fixed lint issue
* Commtent Content: Add Border Support
* Added style at root level
* fixed lint issue
* Added Border box in Editor Side
* Added suggested changes
* Fixed Conflict Issue
* Removed Duplicate css
* remove editor style
Co-authored-by: shail-mehta
Co-authored-by: t-hamano
Co-authored-by: aaronrobertshaw
---
.../block-library/src/comment-content/block.json | 15 ++++++++++++++-
.../block-library/src/comment-content/style.scss | 5 +++++
packages/block-library/src/style.scss | 1 +
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/packages/block-library/src/comment-content/block.json b/packages/block-library/src/comment-content/block.json
index 9ac4b5453365bb..0e812299a45e89 100644
--- a/packages/block-library/src/comment-content/block.json
+++ b/packages/block-library/src/comment-content/block.json
@@ -35,6 +35,18 @@
"fontSize": true
}
},
+ "__experimentalBorder": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true,
+ "__experimentalDefaultControls": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true
+ }
+ },
"spacing": {
"padding": [ "horizontal", "vertical" ],
"__experimentalDefaultControls": {
@@ -42,5 +54,6 @@
}
},
"html": false
- }
+ },
+ "style": "wp-block-comment-content"
}
diff --git a/packages/block-library/src/comment-content/style.scss b/packages/block-library/src/comment-content/style.scss
index dd9c4b22ae99c1..a94048de6014d3 100644
--- a/packages/block-library/src/comment-content/style.scss
+++ b/packages/block-library/src/comment-content/style.scss
@@ -3,3 +3,8 @@
font-size: 0.875em;
line-height: 1.5;
}
+
+.wp-block-comment-content {
+ // This block has customizable padding, border-box makes that more predictable.
+ box-sizing: border-box;
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index df1337d7eb25c0..86fe5a3eefabaf 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -11,6 +11,7 @@
@import "./comments-pagination/style.scss";
@import "./comment-template/style.scss";
@import "./comment-date/style.scss";
+@import "./comment-content/style.scss";
@import "./cover/style.scss";
@import "./details/style.scss";
@import "./embed/style.scss";
From 38c233fef036073ce4aded4ae9f5ea3b43a45d48 Mon Sep 17 00:00:00 2001
From: Ramon
Date: Thu, 15 Aug 2024 16:12:15 +1000
Subject: [PATCH 094/126] Style engine: update type for getCSSValueFromRawStyle
(#64528)
* Use a generic type parameter StyleValue to describe argument and return value. By default, StyleValue is set to string. This allows the function to be flexible with the type of styleValue, but it defaults to handling strings.
Co-authored-by: ramonjd
Co-authored-by: kevin940726
---
packages/style-engine/README.md | 8 +++++---
packages/style-engine/src/styles/utils.ts | 18 ++++++++++--------
2 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/packages/style-engine/README.md b/packages/style-engine/README.md
index 14e8edbd0dec26..a3e302fab4bb9a 100644
--- a/packages/style-engine/README.md
+++ b/packages/style-engine/README.md
@@ -260,7 +260,9 @@ _Changelog_
### getCSSValueFromRawStyle
-Returns a WordPress CSS custom var value from incoming style preset value. The preset value follows the pattern `var:description|context|slug`.
+Returns a WordPress CSS custom var value from incoming style preset value, if one is detected.
+
+The preset value is a string and follows the pattern `var:description|context|slug`.
Example:
@@ -268,11 +270,11 @@ Example:
_Parameters_
-- _styleValue_ `string | any`: A raw style value.
+- _styleValue_ `StyleValue`: A string representing a raw CSS value. Non-strings won't be processed.
_Returns_
-- `string | unknown`: A CSS var value.
+- `StyleValue`: A CSS custom var value if the incoming style value is a preset value.
diff --git a/packages/style-engine/src/styles/utils.ts b/packages/style-engine/src/styles/utils.ts
index 0c9b40c384b101..74a18d2c2a6e45 100644
--- a/packages/style-engine/src/styles/utils.ts
+++ b/packages/style-engine/src/styles/utils.ts
@@ -127,21 +127,23 @@ export function generateBoxRules(
}
/**
- * Returns a WordPress CSS custom var value from incoming style preset value.
- * The preset value follows the pattern `var:description|context|slug`.
+ * Returns a WordPress CSS custom var value from incoming style preset value,
+ * if one is detected.
+ *
+ * The preset value is a string and follows the pattern `var:description|context|slug`.
*
* Example:
*
* `getCSSValueFromRawStyle( 'var:preset|color|heavenlyBlue' )` // returns 'var(--wp--preset--color--heavenly-blue)'
*
- * @param styleValue A raw style value.
+ * @param styleValue A string representing a raw CSS value. Non-strings won't be processed.
*
- * @return A CSS var value.
+ * @return A CSS custom var value if the incoming style value is a preset value.
*/
-export function getCSSValueFromRawStyle(
- styleValue: string | any
-): string | unknown {
+export function getCSSValueFromRawStyle< StyleValue = string >(
+ styleValue: StyleValue
+): StyleValue {
if (
typeof styleValue === 'string' &&
styleValue.startsWith( VARIABLE_REFERENCE_PREFIX )
@@ -160,7 +162,7 @@ export function getCSSValueFromRawStyle(
} )
)
.join( VARIABLE_PATH_SEPARATOR_TOKEN_STYLE );
- return `var(--wp--${ variable })`;
+ return `var(--wp--${ variable })` as StyleValue;
}
return styleValue;
}
From 8fc4c29ef51b1e0dd07000ee4374116a7bad3b71 Mon Sep 17 00:00:00 2001
From: Robert Anderson
Date: Thu, 15 Aug 2024 16:18:53 +1000
Subject: [PATCH 095/126] GridPopover: Avoid over-selecting by using a new
getBlockStyles private selector (#64386)
* Add getBlockStyles selector so avoid over-selecting in GridPopover
* Will an empty commit make the tests run?
---
.../src/components/grid/grid-visualizer.js | 17 ++--
.../src/store/private-selectors.js | 21 +++++
.../src/store/test/private-selectors.js | 89 +++++++++++++++++++
3 files changed, 122 insertions(+), 5 deletions(-)
diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js
index fad2f5cfb14835..81da0457ffc5ca 100644
--- a/packages/block-editor/src/components/grid/grid-visualizer.js
+++ b/packages/block-editor/src/components/grid/grid-visualizer.js
@@ -19,6 +19,7 @@ import { range, GridRect, getGridInfo } from './utils';
import { store as blockEditorStore } from '../../store';
import { useGetNumberOfBlocksBeforeCell } from './use-get-number-of-blocks-before-cell';
import ButtonBlockAppender from '../button-block-appender';
+import { unlock } from '../../lock-unlock';
export function GridVisualizer( { clientId, contentRef, parentLayout } ) {
const isDistractionFree = useSelect(
@@ -118,19 +119,25 @@ const GridVisualizerGrid = forwardRef(
function ManualGridVisualizer( { gridClientId, gridInfo } ) {
const [ highlightedRect, setHighlightedRect ] = useState( null );
- const gridItems = useSelect(
- ( select ) => select( blockEditorStore ).getBlocks( gridClientId ),
+ const gridItemStyles = useSelect(
+ ( select ) => {
+ const { getBlockOrder, getBlockStyles } = unlock(
+ select( blockEditorStore )
+ );
+ const blockOrder = getBlockOrder( gridClientId );
+ return getBlockStyles( blockOrder );
+ },
[ gridClientId ]
);
const occupiedRects = useMemo( () => {
const rects = [];
- for ( const block of gridItems ) {
+ for ( const style of Object.values( gridItemStyles ) ) {
const {
columnStart,
rowStart,
columnSpan = 1,
rowSpan = 1,
- } = block.attributes.style?.layout || {};
+ } = style?.layout ?? {};
if ( ! columnStart || ! rowStart ) {
continue;
}
@@ -144,7 +151,7 @@ function ManualGridVisualizer( { gridClientId, gridInfo } ) {
);
}
return rects;
- }, [ gridItems ] );
+ }, [ gridItemStyles ] );
return range( 1, gridInfo.numRows ).map( ( row ) =>
range( 1, gridInfo.numColumns ).map( ( column ) => {
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index dcd315a0ae2804..7f0a4c58ab6747 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -515,3 +515,24 @@ export function getTemporarilyEditingFocusModeToRevert( state ) {
export function getInserterSearchInputRef( state ) {
return state.inserterSearchInputRef;
}
+
+/**
+ * Returns the style attributes of multiple blocks.
+ *
+ * @param {Object} state Global application state.
+ * @param {string[]} clientIds An array of block client IDs.
+ *
+ * @return {Object} An object where keys are client IDs and values are the corresponding block styles or undefined.
+ */
+export const getBlockStyles = createSelector(
+ ( state, clientIds ) =>
+ clientIds.reduce( ( styles, clientId ) => {
+ styles[ clientId ] = state.blocks.attributes.get( clientId )?.style;
+ return styles;
+ }, {} ),
+ ( state, clientIds ) => [
+ ...clientIds.map(
+ ( clientId ) => state.blocks.attributes.get( clientId )?.style
+ ),
+ ]
+);
diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js
index 185da1ffb98046..45432b750bb9eb 100644
--- a/packages/block-editor/src/store/test/private-selectors.js
+++ b/packages/block-editor/src/store/test/private-selectors.js
@@ -9,6 +9,7 @@ import {
getEnabledBlockParents,
getExpandedBlock,
isDragging,
+ getBlockStyles,
} from '../private-selectors';
import { getBlockEditingMode } from '../selectors';
@@ -509,4 +510,92 @@ describe( 'private selectors', () => {
);
} );
} );
+
+ describe( 'getBlockStyles', () => {
+ it( 'should return an empty object when no client IDs are provided', () => {
+ const state = {
+ blocks: {
+ attributes: new Map(),
+ },
+ };
+ const result = getBlockStyles( state, [] );
+ expect( result ).toEqual( {} );
+ } );
+
+ it( 'should return styles for a single block', () => {
+ const state = {
+ blocks: {
+ attributes: new Map( [
+ [ 'block-1', { style: { color: 'red' } } ],
+ ] ),
+ },
+ };
+ const result = getBlockStyles( state, [ 'block-1' ] );
+ expect( result ).toEqual( {
+ 'block-1': { color: 'red' },
+ } );
+ } );
+
+ it( 'should return styles for multiple blocks', () => {
+ const state = {
+ blocks: {
+ attributes: new Map( [
+ [ 'block-1', { style: { color: 'red' } } ],
+ [ 'block-2', { style: { fontSize: '16px' } } ],
+ [ 'block-3', { style: { margin: '10px' } } ],
+ ] ),
+ },
+ };
+ const result = getBlockStyles( state, [
+ 'block-1',
+ 'block-2',
+ 'block-3',
+ ] );
+ expect( result ).toEqual( {
+ 'block-1': { color: 'red' },
+ 'block-2': { fontSize: '16px' },
+ 'block-3': { margin: '10px' },
+ } );
+ } );
+
+ it( 'should return undefined for blocks without styles', () => {
+ const state = {
+ blocks: {
+ attributes: new Map( [
+ [ 'block-1', { style: { color: 'red' } } ],
+ [ 'block-2', {} ],
+ [ 'block-3', { style: { margin: '10px' } } ],
+ ] ),
+ },
+ };
+ const result = getBlockStyles( state, [
+ 'block-1',
+ 'block-2',
+ 'block-3',
+ ] );
+ expect( result ).toEqual( {
+ 'block-1': { color: 'red' },
+ 'block-2': undefined,
+ 'block-3': { margin: '10px' },
+ } );
+ } );
+
+ it( 'should return undefined for non-existent blocks', () => {
+ const state = {
+ blocks: {
+ attributes: new Map( [
+ [ 'block-1', { style: { color: 'red' } } ],
+ ] ),
+ },
+ };
+ const result = getBlockStyles( state, [
+ 'block-1',
+ 'non-existent-block',
+ ] );
+ expect( result ).toEqual( {
+ 'block-1': { color: 'red' },
+ 'non-existent-block': undefined,
+ } );
+ } );
+ } );
} );
From 1dfcd70549bacd2ec26623d4ecf62559ff40b72c Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Thu, 15 Aug 2024 09:28:46 +0200
Subject: [PATCH 096/126] Paste: fix blob uploading (#64479)
---
packages/block-library/src/image/transforms.js | 8 +++++++-
packages/block-library/src/video/transforms.js | 6 +++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/packages/block-library/src/image/transforms.js b/packages/block-library/src/image/transforms.js
index fda29a2ca2530a..0e1dfb6ee9da44 100644
--- a/packages/block-library/src/image/transforms.js
+++ b/packages/block-library/src/image/transforms.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { createBlobURL } from '@wordpress/blob';
+import { createBlobURL, isBlobURL } from '@wordpress/blob';
import { createBlock, getBlockAttributes } from '@wordpress/blocks';
import { dispatch } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
@@ -123,6 +123,12 @@ const transforms = {
anchor,
}
);
+
+ if ( isBlobURL( attributes.url ) ) {
+ attributes.blob = attributes.url;
+ delete attributes.url;
+ }
+
return createBlock( 'core/image', attributes );
},
},
diff --git a/packages/block-library/src/video/transforms.js b/packages/block-library/src/video/transforms.js
index c1bbb3431aaf37..4325cd1e7e7e9d 100644
--- a/packages/block-library/src/video/transforms.js
+++ b/packages/block-library/src/video/transforms.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { createBlobURL } from '@wordpress/blob';
+import { createBlobURL, isBlobURL } from '@wordpress/blob';
import { createBlock } from '@wordpress/blocks';
const transforms = {
@@ -92,6 +92,10 @@ const transforms = {
poster: videoElement.getAttribute( 'poster' ) || undefined,
src: videoElement.getAttribute( 'src' ) || undefined,
};
+ if ( isBlobURL( attributes.src ) ) {
+ attributes.blob = attributes.src;
+ delete attributes.src;
+ }
return createBlock( 'core/video', attributes );
},
},
From f5f0c67ca561da46fe5430df75c4beffc8b1ce8d Mon Sep 17 00:00:00 2001
From: Dave Smith
Date: Thu, 15 Aug 2024 10:04:12 +0100
Subject: [PATCH 097/126] Add private isZoomOutMode selector (#64503)
---
packages/block-editor/src/store/private-selectors.js | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index 7f0a4c58ab6747..58479d69cbfbe7 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -536,3 +536,14 @@ export const getBlockStyles = createSelector(
),
]
);
+
+/**
+ * Returns whether zoom out mode is enabled.
+ *
+ * @param {Object} state Editor state.
+ *
+ * @return {boolean} Is zoom out mode enabled.
+ */
+export function isZoomOutMode( state ) {
+ return state.editorMode === 'zoom-out';
+}
From 8fc3df806a7e3494937477155fad18f92fdb9538 Mon Sep 17 00:00:00 2001
From: Shail Mehta
Date: Thu, 15 Aug 2024 14:53:48 +0530
Subject: [PATCH 098/126] Post Author Name: Add Border Support (#64530)
* Post Author Name: Border Support
* Post Author Name: Border Support
* Keep Spacing Controle bydefault
* Added Border Control Display By Default
Co-authored-by: shail-mehta
Co-authored-by: aaronrobertshaw
---
.../block-library/src/post-author-name/block.json | 15 ++++++++++++++-
.../block-library/src/post-author-name/style.scss | 4 ++++
packages/block-library/src/style.scss | 1 +
3 files changed, 19 insertions(+), 1 deletion(-)
create mode 100644 packages/block-library/src/post-author-name/style.scss
diff --git a/packages/block-library/src/post-author-name/block.json b/packages/block-library/src/post-author-name/block.json
index 31874ddbf9bc5d..68d2c49bd91056 100644
--- a/packages/block-library/src/post-author-name/block.json
+++ b/packages/block-library/src/post-author-name/block.json
@@ -53,6 +53,19 @@
},
"interactivity": {
"clientNavigation": true
+ },
+ "__experimentalBorder": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true,
+ "__experimentalDefaultControls": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true
+ }
}
- }
+ },
+ "style": "wp-block-post-author-name"
}
diff --git a/packages/block-library/src/post-author-name/style.scss b/packages/block-library/src/post-author-name/style.scss
new file mode 100644
index 00000000000000..0f57b30490fa68
--- /dev/null
+++ b/packages/block-library/src/post-author-name/style.scss
@@ -0,0 +1,4 @@
+.wp-block-post-author-name {
+ // This block has customizable padding, border-box makes that more predictable.
+ box-sizing: border-box;
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 86fe5a3eefabaf..338753ae53fcbf 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -40,6 +40,7 @@
@import "./post-terms/style.scss";
@import "./post-time-to-read/style.scss";
@import "./post-title/style.scss";
+@import "./post-author-name/style.scss";
@import "./preformatted/style.scss";
@import "./pullquote/style.scss";
@import "./post-template/style.scss";
From 787895eb794432803d45f88c4da9addb7bda0202 Mon Sep 17 00:00:00 2001
From: Shail Mehta
Date: Thu, 15 Aug 2024 16:18:23 +0530
Subject: [PATCH 099/126] Updated small typo in compat.php file (#64535)
Co-authored-by: shail-mehta
Co-authored-by: t-hamano
---
lib/compat/wordpress-6.7/compat.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php
index 7021cab2053eff..edc8e3fa5fb03f 100644
--- a/lib/compat/wordpress-6.7/compat.php
+++ b/lib/compat/wordpress-6.7/compat.php
@@ -64,7 +64,7 @@ function ( $registered_template ) use ( $template_files ) {
* Hooks into `get_block_template` to add the `plugin` property when necessary.
*
* @param [WP_Block_Template|null] $block_template The found block template, or null if there isn’t one.
- * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was reigstered by a plugin.
+ * @return [WP_Block_Template|null] The block template that was already found with the plugin property defined if it was registered by a plugin.
*/
function _gutenberg_add_block_template_plugin_attribute( $block_template ) {
if ( $block_template ) {
From cdbd5829e24459f9f2e23132ae46824ec1571c85 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Thu, 15 Aug 2024 20:10:38 +0900
Subject: [PATCH 100/126] ToggleGroupControl: Add lint rule for 40px size prop
usage (#64524)
* Fix in blocks
* Fix in Navigation block
* Fix in Comments Pagination block
* Fix in Grid layout
* Fix in Flex layout
* Fix in Constrained layout (e.g. Group block)
* Add lint rule
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: ciampo
---
.eslintrc.js | 1 +
packages/block-editor/src/layouts/constrained.js | 1 +
packages/block-editor/src/layouts/flex.js | 2 ++
packages/block-editor/src/layouts/grid.js | 1 +
.../comments-pagination-arrow-controls.js | 1 +
packages/block-library/src/navigation/edit/index.js | 6 +++---
.../src/navigation/edit/overlay-menu-preview.js | 2 ++
packages/block-library/src/navigation/editor.scss | 5 +++++
.../src/post-featured-image/dimension-controls.js | 1 +
packages/block-library/src/post-navigation-link/edit.js | 1 +
.../src/query-pagination/query-pagination-arrow-controls.js | 1 +
11 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index eb2d2db47e4cab..94b443853e0a81 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -315,6 +315,7 @@ module.exports = {
'BorderControl',
'DimensionControl',
'FontSizePicker',
+ 'ToggleGroupControl',
].map( ( componentName ) => ( {
// Falsy `__next40pxDefaultSize` without a non-default `size` prop.
selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"][value.expression.value!=false])):not(:has(JSXAttribute[name.name="size"][value.value!="default"]))`,
diff --git a/packages/block-editor/src/layouts/constrained.js b/packages/block-editor/src/layouts/constrained.js
index 2e671e8e53975a..b48e331792bd8a 100644
--- a/packages/block-editor/src/layouts/constrained.js
+++ b/packages/block-editor/src/layouts/constrained.js
@@ -125,6 +125,7 @@ export default {
) }
{ allowJustification && (
>
) }
- { __( 'Overlay Menu' ) }
setAttributes( { icon: value } ) }
diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss
index b279dc08cfe6e7..24480bb1592516 100644
--- a/packages/block-library/src/navigation/editor.scss
+++ b/packages/block-library/src/navigation/editor.scss
@@ -628,3 +628,8 @@ body.editor-styles-wrapper .wp-block-navigation__responsive-container.is-menu-op
.wp-block-navigation__menu-inspector-controls__empty-message {
margin-left: 24px;
}
+
+.wp-block-navigation__overlay-menu-icon-toggle-group {
+ // Counteract the margin added by the block inspector.
+ margin-bottom: $grid-unit-20;
+}
diff --git a/packages/block-library/src/post-featured-image/dimension-controls.js b/packages/block-library/src/post-featured-image/dimension-controls.js
index c8e8c0005cfef5..4d272d3d8bb415 100644
--- a/packages/block-library/src/post-featured-image/dimension-controls.js
+++ b/packages/block-library/src/post-featured-image/dimension-controls.js
@@ -203,6 +203,7 @@ const DimensionControls = ( {
panelId={ clientId }
>
) }
Date: Thu, 15 Aug 2024 14:01:48 +0200
Subject: [PATCH 101/126] Fix: Changing sorting direction on patterns does
nothing. (#64508)
Co-authored-by: jorgefilipecosta
Co-authored-by: t-hamano
---
.../src/components/dataviews-view-config/index.tsx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx
index 0aae714c5ce27a..69b4cba7763262 100644
--- a/packages/dataviews/src/components/dataviews-view-config/index.tsx
+++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx
@@ -140,7 +140,7 @@ function SortFieldControl() {
}
function SortDirectionControl() {
- const { view, onChangeView } = useContext( DataViewsContext );
+ const { view, fields, onChangeView } = useContext( DataViewsContext );
return (
{
- if ( ! view?.sort?.field ) {
- return;
- }
if ( newDirection === 'asc' || newDirection === 'desc' ) {
onChangeView( {
...view,
sort: {
direction: newDirection,
- field: view.sort.field,
+ field:
+ view.sort?.field ||
+ // If there is no field assigned as the sorting field assign the first sortable field.
+ fields.find(
+ ( field ) => field.enableSorting !== false
+ )?.id ||
+ '',
},
} );
return;
From cb224d4a5a73ebe3d616d54d72a1590dbbdfc09f Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Thu, 15 Aug 2024 21:43:35 +0900
Subject: [PATCH 102/126] Quote Block: Add align support (#64188)
* Quote Block: Add align support
* Inherit alignment when transforming from pullquote block
* Inherit alignment when transforming to pullquote block
* Try to fix mobile unit test
* Try to fix mobile unit test
* Try to fix mobile unit test
Co-authored-by: t-hamano
Co-authored-by: ramonjd
Co-authored-by: geriux
---
docs/reference-guides/core-blocks.md | 2 +-
packages/block-library/src/quote/block.json | 1 +
.../src/quote/test/transforms.native.js | 2 +-
packages/block-library/src/quote/transforms.js | 13 +++++++++++--
.../src/mobile/utils/alignments.native.js | 1 +
5 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 5beb712c80a113..b9cae44550181c 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -783,7 +783,7 @@ Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Ju
- **Name:** core/quote
- **Category:** text
-- **Supports:** anchor, background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align (full, left, right, wide), anchor, background (backgroundImage, backgroundSize), color (background, gradients, heading, link, text), dimensions (minHeight), interactivity (clientNavigation), layout (~~allowEditing~~), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** citation, textAlign, value
## Read More
diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json
index c695c938923ae7..0f9ec97422f64b 100644
--- a/packages/block-library/src/quote/block.json
+++ b/packages/block-library/src/quote/block.json
@@ -28,6 +28,7 @@
},
"supports": {
"anchor": true,
+ "align": [ "left", "right", "wide", "full" ],
"html": false,
"background": {
"backgroundImage": true,
diff --git a/packages/block-library/src/quote/test/transforms.native.js b/packages/block-library/src/quote/test/transforms.native.js
index 25030e0a018d41..3d2bc8ae609182 100644
--- a/packages/block-library/src/quote/test/transforms.native.js
+++ b/packages/block-library/src/quote/test/transforms.native.js
@@ -14,7 +14,7 @@ import {
const block = 'Quote';
const initialHtml = `
-
+
"This will make running your own blog a viable alternative again."
— Adrian Zumbrunnen
diff --git a/packages/block-library/src/quote/transforms.js b/packages/block-library/src/quote/transforms.js
index f9b3970433fad6..c960759691bf16 100644
--- a/packages/block-library/src/quote/transforms.js
+++ b/packages/block-library/src/quote/transforms.js
@@ -9,10 +9,18 @@ const transforms = {
{
type: 'block',
blocks: [ 'core/pullquote' ],
- transform: ( { value, citation, anchor, fontSize, style } ) => {
+ transform: ( {
+ value,
+ align,
+ citation,
+ anchor,
+ fontSize,
+ style,
+ } ) => {
return createBlock(
'core/quote',
{
+ align,
citation,
anchor,
fontSize,
@@ -95,7 +103,7 @@ const transforms = {
);
},
transform: (
- { citation, anchor, fontSize, style },
+ { align, citation, anchor, fontSize, style },
innerBlocks
) => {
const value = innerBlocks
@@ -103,6 +111,7 @@ const transforms = {
.join( ' ' );
return createBlock( 'core/pullquote', {
value,
+ align,
citation,
anchor,
fontSize,
diff --git a/packages/components/src/mobile/utils/alignments.native.js b/packages/components/src/mobile/utils/alignments.native.js
index bc42385988a5d1..f1f737d7ed367a 100644
--- a/packages/components/src/mobile/utils/alignments.native.js
+++ b/packages/components/src/mobile/utils/alignments.native.js
@@ -13,6 +13,7 @@ export const WIDE_ALIGNMENTS = {
'core/image',
'core/separator',
'core/media-text',
+ 'core/quote',
'core/pullquote',
],
};
From d530eca033c91cacbd047eda685c45af787913b4 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Thu, 15 Aug 2024 16:11:22 +0200
Subject: [PATCH 103/126] Update: Move item size control to the new view config
UI. (#64380)
Co-authored-by: jorgefilipecosta
Co-authored-by: jameskoster
Co-authored-by: t-hamano
Co-authored-by: jasmussen
Co-authored-by: tyxla
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
.../dataviews-view-config/index.tsx | 40 +++++++--
.../src/components/dataviews/index.tsx | 10 +--
.../dataviews-layouts/grid/density-picker.tsx | 84 ++++---------------
.../src/dataviews-layouts/grid/style.scss | 4 -
4 files changed, 52 insertions(+), 86 deletions(-)
diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx
index 69b4cba7763262..034f618aea00ff 100644
--- a/packages/dataviews/src/components/dataviews-view-config/index.tsx
+++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx
@@ -30,11 +30,17 @@ import warning from '@wordpress/warning';
/**
* Internal dependencies
*/
-import { SORTING_DIRECTIONS, sortIcons, sortLabels } from '../../constants';
+import {
+ SORTING_DIRECTIONS,
+ LAYOUT_GRID,
+ sortIcons,
+ sortLabels,
+} from '../../constants';
import { VIEW_LAYOUTS, getMandatoryFields } from '../../dataviews-layouts';
import type { SupportedLayouts } from '../../types';
import DataViewsContext from '../dataviews-context';
import { unlock } from '../../lock-unlock';
+import DensityPicker from '../../dataviews-layouts/grid/density-picker';
const {
DropdownMenuV2: DropdownMenu,
@@ -101,10 +107,6 @@ function ViewTypeMenu( {
);
}
-interface ViewActionsProps {
- defaultLayouts?: SupportedLayouts;
-}
-
function SortFieldControl() {
const { view, fields, onChangeView } = useContext( DataViewsContext );
const orderOptions = useMemo( () => {
@@ -305,7 +307,14 @@ function SettingsSection( {
);
}
-function DataviewsViewConfigContent() {
+function DataviewsViewConfigContent( {
+ density,
+ setDensity,
+}: {
+ density: number;
+ setDensity: React.Dispatch< React.SetStateAction< number > >;
+} ) {
+ const { view } = useContext( DataViewsContext );
return (
@@ -313,6 +322,12 @@ function DataviewsViewConfigContent() {
+ { view.type === LAYOUT_GRID && (
+
+ ) }
@@ -323,8 +338,14 @@ function DataviewsViewConfigContent() {
}
function _DataViewsViewConfig( {
+ density,
+ setDensity,
defaultLayouts = { list: {}, grid: {}, table: {} },
-}: ViewActionsProps ) {
+}: {
+ density: number;
+ setDensity: React.Dispatch< React.SetStateAction< number > >;
+ defaultLayouts?: SupportedLayouts;
+} ) {
const [ isShowingViewPopover, setIsShowingViewPopover ] =
useState< boolean >( false );
@@ -346,7 +367,10 @@ function _DataViewsViewConfig( {
} }
focusOnMount
>
-
+
) }
diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx
index 337912b04e59c5..81f901f0859bbc 100644
--- a/packages/dataviews/src/components/dataviews/index.tsx
+++ b/packages/dataviews/src/components/dataviews/index.tsx
@@ -27,8 +27,6 @@ import DataViewsViewConfig from '../dataviews-view-config';
import { normalizeFields } from '../../normalize-fields';
import type { Action, Field, View, SupportedLayouts } from '../../types';
import type { SelectionOrUpdater } from '../../private-types';
-import DensityPicker from '../../dataviews-layouts/grid/density-picker';
-import { LAYOUT_GRID } from '../../constants';
type ItemWithId = { id: string };
@@ -133,12 +131,6 @@ export default function DataViews< Item >( {
isShowingFilter={ isShowingFilter }
/>
- { view.type === LAYOUT_GRID && (
-
- ) }
( {
>
{ header }
diff --git a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx b/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx
index 364d764e343470..8f0782878af224 100644
--- a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx
+++ b/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx
@@ -1,10 +1,9 @@
/**
* WordPress dependencies
*/
-import { RangeControl, Button } from '@wordpress/components';
+import { RangeControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useViewportMatch } from '@wordpress/compose';
-import { plus, reset } from '@wordpress/icons';
import { useEffect } from '@wordpress/element';
const viewportBreaks = {
@@ -40,21 +39,6 @@ function useViewPortBreakpoint() {
return null;
}
-// Value is number from 0 to 100 representing how big an item is in the grid
-// 100 being the biggest and 0 being the smallest.
-// The size is relative to the viewport size, if one a given viewport the
-// number of allowed items in a grid is 3 to 6 a 0 ( the smallest ) will mean that the grid will
-// have 6 items in a row, a 100 ( the biggest ) will mean that the grid will have 3 items in a row.
-// A value of 75 will mean that the grid will have 4 items in a row.
-function getRangeValue(
- density: number,
- breakValues: { min: number; max: number; default: number }
-) {
- const inverseDensity = breakValues.max - density;
- const max = breakValues.max - breakValues.min;
- return Math.round( ( inverseDensity * 100 ) / max );
-}
-
export default function DensityPicker( {
density,
setDensity,
@@ -78,59 +62,27 @@ export default function DensityPicker( {
return _density;
} );
}, [ setDensity, viewport ] );
+ const breakValues = viewportBreaks[ viewport || 'mobile' ];
+ const densityToUse = density || breakValues.default;
+
if ( ! viewport ) {
return null;
}
- const breakValues = viewportBreaks[ viewport ];
- const densityToUse = density || breakValues.default;
- const rangeValue = getRangeValue( densityToUse, breakValues );
- const step = 100 / ( breakValues.max - breakValues.min + 1 );
return (
- <>
- {
- setDensity( densityToUse + 1 );
- } }
- />
- {
- const inverseValue = 100 - value;
- setDensity(
- Math.round(
- ( inverseValue *
- ( breakValues.max - breakValues.min ) ) /
- 100 +
- breakValues.min
- )
- );
- } }
- step={ step }
- />
- = 100 }
- accessibleWhenDisabled
- label={ __( 'Increase size' ) }
- onClick={ () => {
- setDensity( densityToUse - 1 );
- } }
- />
- >
+ {
+ setDensity( breakValues.max + breakValues.min - value );
+ } }
+ step={ 1 }
+ />
);
}
diff --git a/packages/dataviews/src/dataviews-layouts/grid/style.scss b/packages/dataviews/src/dataviews-layouts/grid/style.scss
index 8f71953e155981..d154d2b614def5 100644
--- a/packages/dataviews/src/dataviews-layouts/grid/style.scss
+++ b/packages/dataviews/src/dataviews-layouts/grid/style.scss
@@ -146,10 +146,6 @@
}
}
-.dataviews-density-picker__range-control {
- width: 200px;
-}
-
.dataviews-view-grid__field-value:empty,
.dataviews-view-grid__field:empty {
display: none;
From 8bee004d9014604d4b9ea6a2cc20634e507ccb14 Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Thu, 15 Aug 2024 19:18:56 +0400
Subject: [PATCH 104/126] Fix flaky block template registration e2e test
(#64541)
Co-authored-by: Mamaduka
---
test/e2e/specs/site-editor/template-registration.spec.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/test/e2e/specs/site-editor/template-registration.spec.js b/test/e2e/specs/site-editor/template-registration.spec.js
index 132e3a8c49a902..3927623b2824dc 100644
--- a/test/e2e/specs/site-editor/template-registration.spec.js
+++ b/test/e2e/specs/site-editor/template-registration.spec.js
@@ -243,8 +243,13 @@ test.describe( 'Block template registration', () => {
test( 'WP default templates can be overridden by plugins', async ( {
page,
+ requestUtils,
} ) => {
- await page.goto( '?page_id=2' );
+ const { id } = await requestUtils.createPage( {
+ title: 'Plugin override page',
+ status: 'publish',
+ } );
+ await page.goto( `?page_id=${ id }` );
await expect(
page.getByText( 'This is a plugin-registered page template.' )
).toBeVisible();
From 8bcd5f01e9921f9a57b51e16ca46b04a83e5f0ab Mon Sep 17 00:00:00 2001
From: Amit Raj <77401999+amitraj2203@users.noreply.github.com>
Date: Thu, 15 Aug 2024 20:53:21 +0530
Subject: [PATCH 105/126] ToolsPanel: Sets column-gap to 16px for grid (#64497)
* feat: sets column-gap to 16px for ToolsPanel grid
* Updates changelog
Co-authored-by: amitraj2203
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
packages/components/CHANGELOG.md | 2 +-
packages/components/src/tools-panel/styles.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index e758e457bdd03e..eb207643cdfcb2 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -28,7 +28,7 @@
- `FocalPointPicker`: Default to new 40px size ([#64456](https://github.com/WordPress/gutenberg/pull/64456)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
- `Popover`: allow `style` prop usage ([#64489](https://github.com/WordPress/gutenberg/pull/64489)).
-
+- `ToolsPanel`: sets column-gap to 16px for ToolsPanel grid ([#64497](https://github.com/WordPress/gutenberg/pull/64497)).
### Bug Fixes
diff --git a/packages/components/src/tools-panel/styles.ts b/packages/components/src/tools-panel/styles.ts
index 1da1003c0462e3..11536e98a128a9 100644
--- a/packages/components/src/tools-panel/styles.ts
+++ b/packages/components/src/tools-panel/styles.ts
@@ -21,7 +21,7 @@ const toolsPanelGrid = {
grid-template-columns: ${ `repeat( ${ columns }, minmax(0, 1fr) )` };
`,
spacing: css`
- column-gap: ${ space( 2 ) };
+ column-gap: ${ space( 4 ) };
row-gap: ${ space( 4 ) };
`,
item: {
From 294b888b6f7ab2b9cfa6c40f5664586265fea689 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Thu, 15 Aug 2024 17:40:59 +0200
Subject: [PATCH 106/126] DataViews: Update: Sort descending button may be
wrongly pressed. (#64547)
Co-authored-by: jorgefilipecosta
---
.../src/components/dataviews-view-config/index.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx
index 034f618aea00ff..e396b1c68203cc 100644
--- a/packages/dataviews/src/components/dataviews-view-config/index.tsx
+++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx
@@ -143,6 +143,10 @@ function SortFieldControl() {
function SortDirectionControl() {
const { view, fields, onChangeView } = useContext( DataViewsContext );
+ let value = view.sort?.direction;
+ if ( ! value && view.sort?.field ) {
+ value = 'desc';
+ }
return (
{
if ( newDirection === 'asc' || newDirection === 'desc' ) {
onChangeView( {
From 9fc46bee0184658a093664b1e514b487fdd78bed Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Thu, 15 Aug 2024 19:55:07 +0400
Subject: [PATCH 107/126] Improve Image block e2e tests (#64537)
Co-authored-by: Mamaduka
Co-authored-by: ellatrix
---
test/e2e/specs/editor/blocks/image.spec.js | 56 +++++++++++++++++-----
1 file changed, 44 insertions(+), 12 deletions(-)
diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js
index c4c725e083625e..43527b48fbf707 100644
--- a/test/e2e/specs/editor/blocks/image.spec.js
+++ b/test/e2e/specs/editor/blocks/image.spec.js
@@ -708,13 +708,14 @@ test.describe( 'Image', () => {
await editor.clickBlockToolbarButton( 'Upload to Media Library' );
- const imageBlock = editor.canvas.locator(
- 'role=document[name="Block: Image"i]'
+ await expect(
+ editor.canvas
+ .locator( 'role=document[name="Block: Image"i]' )
+ .locator( 'img[src^="http"]' )
+ ).toHaveAttribute(
+ 'src',
+ expect.stringMatching( /\/wp-content\/uploads\// )
);
- const image = imageBlock.locator( 'img[src^="http"]' );
- const src = await image.getAttribute( 'src' );
-
- expect( src ).toMatch( /\/wp-content\/uploads\// );
} );
test( 'should upload through prepublish panel', async ( {
@@ -736,14 +737,45 @@ test.describe( 'Image', () => {
.click();
await expect( page.locator( '.components-spinner' ) ).toHaveCount( 0 );
-
- const imageBlock = editor.canvas.locator(
- 'role=document[name="Block: Image"i]'
+ await expect(
+ editor.canvas
+ .locator( 'role=document[name="Block: Image"i]' )
+ .locator( 'img[src^="http"]' )
+ ).toHaveAttribute(
+ 'src',
+ expect.stringMatching( /\/wp-content\/uploads\// )
);
- const image = imageBlock.locator( 'img[src^="http"]' );
- const src = await image.getAttribute( 'src' );
+ } );
- expect( src ).toMatch( /\/wp-content\/uploads\// );
+ test( 'uploads data url through blobs from raw handling', async ( {
+ editor,
+ page,
+ pageUtils,
+ } ) => {
+ const blobUrl = await page.evaluate( async () => {
+ const canvas = document.createElement( 'canvas' );
+ canvas.width = 20;
+ canvas.height = 20;
+
+ const ctx = canvas.getContext( '2d' );
+ ctx.fillStyle = 'red';
+ ctx.fillRect( 0, 0, 20, 20 );
+
+ return canvas.toDataURL( 'image/png' );
+ } );
+
+ pageUtils.setClipboardData( { html: ` ` } );
+
+ await pageUtils.pressKeys( 'primary+v' );
+
+ await expect(
+ editor.canvas
+ .locator( 'role=document[name="Block: Image"i]' )
+ .locator( 'img[src^="http"]' )
+ ).toHaveAttribute(
+ 'src',
+ expect.stringMatching( /\/wp-content\/uploads\// )
+ );
} );
test( 'should have keyboard navigable link UI popover', async ( {
From 8559790a6d4bd3192773368a281d7320c3de24b1 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Fri, 16 Aug 2024 00:58:09 +0900
Subject: [PATCH 108/126] SelectControl: Add lint rule for 40px size prop usage
(#64486)
* SelectControl: Add lint rule for 40px size prop usage
* Add lint rule
* Fix in Author block
* Fix in File block
* Fix in Audio block
* Fix in Archives block
* Fix formatting
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: ciampo
---
.eslintrc.js | 16 +++++++++-------
.../src/components/font-family/README.md | 13 +++++++++++--
.../src/components/font-family/index.js | 3 +++
.../responsive-block-control/test/index.js | 1 +
packages/block-library/src/archives/edit.js | 1 +
packages/block-library/src/audio/edit.js | 7 ++++---
packages/block-library/src/file/inspector.js | 1 +
packages/block-library/src/form/edit.js | 2 ++
packages/block-library/src/post-author/edit.js | 2 ++
.../post-featured-image/dimension-controls.js | 4 ++++
.../src/template-part/edit/advanced-controls.js | 2 ++
.../block-library/src/video/tracks-editor.js | 2 ++
.../legacy-widget/edit/widget-type-selector.js | 2 ++
13 files changed, 44 insertions(+), 12 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 94b443853e0a81..2cdbb5c056cb14 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -324,13 +324,15 @@ module.exports = {
' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
} ) ),
// Temporary rules until all existing components have the `__next40pxDefaultSize` prop.
- ...[ 'TextControl' ].map( ( componentName ) => ( {
- // Not strict. Allows pre-existing __next40pxDefaultSize={ false } usage until they are all manually updated.
- selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"])):not(:has(JSXAttribute[name.name="size"]))`,
- message:
- componentName +
- ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
- } ) ),
+ ...[ 'SelectControl', 'TextControl' ].map(
+ ( componentName ) => ( {
+ // Not strict. Allows pre-existing __next40pxDefaultSize={ false } usage until they are all manually updated.
+ selector: `JSXOpeningElement[name.name="${ componentName }"]:not(:has(JSXAttribute[name.name="__next40pxDefaultSize"])):not(:has(JSXAttribute[name.name="size"]))`,
+ message:
+ componentName +
+ ' should have the `__next40pxDefaultSize` prop to opt-in to the new default size.',
+ } )
+ ),
],
},
},
diff --git a/packages/block-editor/src/components/font-family/README.md b/packages/block-editor/src/components/font-family/README.md
index 6b37459fd127e7..63965f509ba534 100644
--- a/packages/block-editor/src/components/font-family/README.md
+++ b/packages/block-editor/src/components/font-family/README.md
@@ -71,9 +71,18 @@ The current font family value.
The rest of the props are passed down to the underlying ` ` instance.
+#### `__next40pxDefaultSize`
+
+- Type: `boolean`
+- Required: No
+- Default: `false`
+
+Start opting into the larger default height that will become the default size in a future version.
+
#### `__nextHasNoMarginBottom`
-- **Type:** `boolean`
-- **Default:** `false`
+- Type: `boolean`
+- Required: No
+- Default: `false`
Start opting into the new margin-free styles that will become the default in a future version.
diff --git a/packages/block-editor/src/components/font-family/index.js b/packages/block-editor/src/components/font-family/index.js
index 90a0412463b3ef..c87a52b4c676d2 100644
--- a/packages/block-editor/src/components/font-family/index.js
+++ b/packages/block-editor/src/components/font-family/index.js
@@ -11,6 +11,8 @@ import { __ } from '@wordpress/i18n';
import { useSettings } from '../use-settings';
export default function FontFamilyControl( {
+ /** Start opting into the larger default height that will become the default size in a future version. */
+ __next40pxDefaultSize = false,
/** Start opting into the new margin-free styles that will become the default in a future version. */
__nextHasNoMarginBottom = false,
value = '',
@@ -50,6 +52,7 @@ export default function FontFamilyControl( {
return (
{
return (
<>
{ /*
- Disable the audio tag if the block is not selected
- so the user clicking on it won't play the
- file or change the position slider when the controls are enabled.
+ Disable the audio tag if the block is not selected
+ so the user clicking on it won't play the
+ file or change the position slider when the controls are enabled.
*/ }
diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js
index 76ed28d124600e..c29f84f60ebace 100644
--- a/packages/block-library/src/file/inspector.js
+++ b/packages/block-library/src/file/inspector.js
@@ -73,6 +73,7 @@ export default function FileBlockInspector( {
) }
{
{ submissionMethod !== 'email' && (
) ) || (
{ showAvatar && (
Date: Thu, 15 Aug 2024 18:00:23 +0200
Subject: [PATCH 109/126] Update: Add marks to preview size control. (#64546)
Co-authored-by: jorgefilipecosta
Co-authored-by: jameskoster
---
.../dataviews-layouts/grid/density-picker.tsx | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx b/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx
index 8f0782878af224..34ddf6c3fe52f3 100644
--- a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx
+++ b/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx
@@ -4,7 +4,7 @@
import { RangeControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useViewportMatch } from '@wordpress/compose';
-import { useEffect } from '@wordpress/element';
+import { useEffect, useMemo } from '@wordpress/element';
const viewportBreaks = {
xhuge: { min: 3, max: 6, default: 5 },
@@ -65,6 +65,19 @@ export default function DensityPicker( {
const breakValues = viewportBreaks[ viewport || 'mobile' ];
const densityToUse = density || breakValues.default;
+ const marks = useMemo(
+ () =>
+ Array.from(
+ { length: breakValues.max - breakValues.min + 1 },
+ ( _, i ) => {
+ return {
+ value: breakValues.min + i,
+ };
+ }
+ ),
+ [ breakValues ]
+ );
+
if ( ! viewport ) {
return null;
}
@@ -76,6 +89,7 @@ export default function DensityPicker( {
showTooltip={ false }
label={ __( 'Preview size' ) }
value={ breakValues.max + breakValues.min - densityToUse }
+ marks={ marks }
min={ breakValues.min }
max={ breakValues.max }
withInputField={ false }
From 38d0500ca8e70622e460ecb62d433b482ef3cb6c Mon Sep 17 00:00:00 2001
From: Rich Tabor
Date: Thu, 15 Aug 2024 13:52:17 -0400
Subject: [PATCH 110/126] Remove inconsistent dark theme focus style on block
selection (#64549)
* remove $dark-theme-focus
* remove is-dark-theme outline color shift on block selection
* remove custom dark theme focus for nav link
* remove template part dark theme focus
Co-authored-by: richtabor
Co-authored-by: Mamaduka
---
packages/base-styles/_colors.scss | 1 -
.../src/components/block-list/content.scss | 10 ----------
packages/block-library/src/navigation-link/editor.scss | 3 ---
packages/block-library/src/template-part/editor.scss | 5 -----
4 files changed, 19 deletions(-)
diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss
index 2ce58b64e43b8c..b296a0f1f03063 100644
--- a/packages/base-styles/_colors.scss
+++ b/packages/base-styles/_colors.scss
@@ -17,7 +17,6 @@ $gray-100: #f0f0f0; // Used for light gray backgrounds.
$white: #fff;
// Opacities & additional colors.
-$dark-theme-focus: $white; // Focus color when the theme is dark.
$dark-gray-placeholder: rgba($gray-900, 0.62);
$medium-gray-placeholder: rgba($gray-900, 0.55);
$light-gray-placeholder: rgba($white, 0.65);
diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss
index 17ebad06c4d78e..c8f24e7efcbd2f 100644
--- a/packages/block-editor/src/components/block-list/content.scss
+++ b/packages/block-editor/src/components/block-list/content.scss
@@ -89,11 +89,6 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b
&::after {
@include selected-block-focus();
z-index: 1;
-
- // Show a light color for dark themes.
- .is-dark-theme & {
- outline-color: $dark-theme-focus;
- }
}
}
@@ -285,11 +280,6 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b
&.block-editor-block-list__block:not([contenteditable]):focus {
&::after {
outline-color: var(--wp-block-synced-color);
-
- // Show a light color for dark themes.
- .is-dark-theme & {
- outline-color: $dark-theme-focus;
- }
}
}
}
diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss
index 6d1dc32e5310b5..84cd6f6d4ee363 100644
--- a/packages/block-library/src/navigation-link/editor.scss
+++ b/packages/block-library/src/navigation-link/editor.scss
@@ -87,9 +87,6 @@
$stop2: 64%;
--wp-underline-color: var(--wp-admin-theme-color);
- .is-dark-theme & {
- --wp-underline-color: #{ $dark-theme-focus };
- }
background-image:
linear-gradient(45deg, transparent ($stop1 - $blur), var(--wp-underline-color) $stop1, var(--wp-underline-color) ($stop1 + $width), transparent ($stop1 + $width + $blur)),
diff --git a/packages/block-library/src/template-part/editor.scss b/packages/block-library/src/template-part/editor.scss
index 71659ce6ba717a..735c9370099d91 100644
--- a/packages/block-library/src/template-part/editor.scss
+++ b/packages/block-library/src/template-part/editor.scss
@@ -35,11 +35,6 @@
&.block-editor-block-list__block:not([contenteditable]):focus {
&::after {
outline-color: var(--wp-block-synced-color);
-
- // Show a light color for dark themes.
- .is-dark-theme & {
- outline-color: $dark-theme-focus;
- }
}
}
}
From 8457cb8eff5bcdf37c6f3518aac9fc15f2a61278 Mon Sep 17 00:00:00 2001
From: Jeremy Herve
Date: Thu, 15 Aug 2024 21:06:50 +0200
Subject: [PATCH 111/126] Props Bot: update to correct event type (#64557)
When wanting to filter by event type, we must use `types`, and not `type`.
Reference: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#issue_comment
Co-authored-by: jeherve
Co-authored-by: aaronjorbin
---
.github/workflows/props-bot.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/props-bot.yml b/.github/workflows/props-bot.yml
index 0f21f47ef14f99..b2332aabb816c7 100644
--- a/.github/workflows/props-bot.yml
+++ b/.github/workflows/props-bot.yml
@@ -18,7 +18,7 @@ on:
# You cannot filter this event for PR comments only.
# However, the logic below does short-circuit the workflow for issues.
issue_comment:
- type:
+ types:
- created
# This event will run everytime a new PR review is initially submitted.
pull_request_review:
From 6de3f66b0a0560969d6328864e2125fdf323c6b7 Mon Sep 17 00:00:00 2001
From: Ramon
Date: Fri, 16 Aug 2024 11:57:00 +1000
Subject: [PATCH 112/126] Background block supports: remove unused properties
in unit tests (#64564)
Co-authored-by: ramonjd
Co-authored-by: andrewserong
---
phpunit/block-supports/background-test.php | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/phpunit/block-supports/background-test.php b/phpunit/block-supports/background-test.php
index 4c949fa82257d3..c698f9e7c2f1c1 100644
--- a/phpunit/block-supports/background-test.php
+++ b/phpunit/block-supports/background-test.php
@@ -122,7 +122,7 @@ public function test_background_block_support( $theme_name, $block_name, $backgr
*/
public function data_background_block_support() {
return array(
- 'background image style is applied to uploaded images' => array(
+ 'background image style is applied' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
'block_name' => 'test/background-rules-are-output',
'background_settings' => array(
@@ -131,7 +131,6 @@ public function data_background_block_support() {
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
- 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
@@ -158,7 +157,6 @@ public function data_background_block_support() {
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
- 'id' => 123,
),
'backgroundRepeat' => 'no-repeat',
'backgroundSize' => 'contain',
@@ -176,7 +174,6 @@ public function data_background_block_support() {
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
- 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
@@ -191,7 +188,6 @@ public function data_background_block_support() {
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
- 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
@@ -206,7 +202,6 @@ public function data_background_block_support() {
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
- 'id' => 123,
),
),
'expected_wrapper' => 'Content
',
From e26396756d7fbd6c78ab5cacf4049dd8d0e8cb58 Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Fri, 16 Aug 2024 12:56:48 +0900
Subject: [PATCH 113/126] Base Styles: Restore deprecated `$dark-theme-focus`
variable (#64563)
Co-authored-by: t-hamano
Co-authored-by: Mamaduka
---
packages/base-styles/_colors.scss | 3 +++
1 file changed, 3 insertions(+)
diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss
index b296a0f1f03063..e65551e48c783c 100644
--- a/packages/base-styles/_colors.scss
+++ b/packages/base-styles/_colors.scss
@@ -25,3 +25,6 @@ $light-gray-placeholder: rgba($white, 0.65);
$alert-yellow: #f0b849;
$alert-red: #cc1818;
$alert-green: #4ab866;
+
+// Deprecated, please avoid using these.
+$dark-theme-focus: $white; // Focus color when the theme is dark.
From 1defa19697561ec203235f2d2beaaf340231d41f Mon Sep 17 00:00:00 2001
From: Ramon
Date: Fri, 16 Aug 2024 15:36:58 +1000
Subject: [PATCH 114/126] Background image: add uploading state and restrict
drag to one image. (#64565)
Add a loading spinner over the background image control
Restrict upload to one image
Co-authored-by: ramonjd
Co-authored-by: Mamaduka
Co-authored-by: andrewserong
---
.../global-styles/background-panel.js | 27 ++++++++++++++++---
.../src/components/global-styles/style.scss | 11 ++++++++
2 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js
index 1373c54764d155..c01db44792989e 100644
--- a/packages/block-editor/src/components/global-styles/background-panel.js
+++ b/packages/block-editor/src/components/global-styles/background-panel.js
@@ -23,6 +23,8 @@ import {
__experimentalHStack as HStack,
__experimentalTruncate as Truncate,
Dropdown,
+ Placeholder,
+ Spinner,
__experimentalDropdownContentWrapper as DropdownContentWrapper,
} from '@wordpress/components';
import { __, _x, sprintf } from '@wordpress/i18n';
@@ -268,6 +270,14 @@ function BackgroundControlsPanel( {
);
}
+function LoadingSpinner() {
+ return (
+
+
+
+ );
+}
+
function BackgroundImageControls( {
onChange,
style,
@@ -277,6 +287,7 @@ function BackgroundImageControls( {
displayInPanel,
defaultValues,
} ) {
+ const [ isUploading, setIsUploading ] = useState( false );
const mediaUpload = useSelect(
( select ) => select( blockEditorStore ).getSettings().mediaUpload,
[]
@@ -289,6 +300,7 @@ function BackgroundImageControls( {
const { createErrorNotice } = useDispatch( noticesStore );
const onUploadError = ( message ) => {
createErrorNotice( message, { type: 'snackbar' } );
+ setIsUploading( false );
};
const resetBackgroundImage = () =>
@@ -303,10 +315,12 @@ function BackgroundImageControls( {
const onSelectMedia = ( media ) => {
if ( ! media || ! media.url ) {
resetBackgroundImage();
+ setIsUploading( false );
return;
}
if ( isBlobURL( media.url ) ) {
+ setIsUploading( true );
return;
}
@@ -349,16 +363,21 @@ function BackgroundImageControls( {
backgroundSize: sizeValue,
} )
);
+ setIsUploading( false );
};
+ // Drag and drop callback, restricting image to one.
const onFilesDrop = ( filesList ) => {
+ if ( filesList?.length > 1 ) {
+ onUploadError(
+ __( 'Only one image can be used as a background image.' )
+ );
+ return;
+ }
mediaUpload( {
allowedTypes: [ IMAGE_BACKGROUND_TYPE ],
filesList,
onFileChange( [ image ] ) {
- if ( isBlobURL( image?.url ) ) {
- return;
- }
onSelectMedia( image );
},
onError: onUploadError,
@@ -393,6 +412,7 @@ function BackgroundImageControls( {
ref={ replaceContainerRef }
className="block-editor-global-styles-background-panel__image-tools-panel-item"
>
+ { isUploading && }
}
variant="secondary"
+ onError={ onUploadError }
>
{ canRemove && (
Date: Fri, 16 Aug 2024 10:44:49 +0400
Subject: [PATCH 115/126] PostFeaturedImage: Disable the media modal while
uploading an image (#64566)
Co-authored-by: Mamaduka
Co-authored-by: ntsekouras
---
packages/editor/src/components/post-featured-image/index.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js
index 5e1874e9b84026..febf56a46778fc 100644
--- a/packages/editor/src/components/post-featured-image/index.js
+++ b/packages/editor/src/components/post-featured-image/index.js
@@ -180,6 +180,8 @@ function PostFeaturedImage( {
: `editor-post-featured-image-${ featuredImageId }-describedby`
}
aria-haspopup="dialog"
+ disabled={ isLoading }
+ accessibleWhenDisabled
>
{ !! featuredImageId && media && (
Date: Fri, 16 Aug 2024 13:13:02 +0530
Subject: [PATCH 116/126] Comment Author Name: Add Border Support (#64550)
Co-authored-by: shail-mehta
Co-authored-by: ramonjd
Co-authored-by: aaronrobertshaw
---
.../src/comment-author-name/block.json | 15 ++++++++++++++-
.../src/comment-author-name/style.scss | 4 ++++
packages/block-library/src/style.scss | 1 +
3 files changed, 19 insertions(+), 1 deletion(-)
create mode 100644 packages/block-library/src/comment-author-name/style.scss
diff --git a/packages/block-library/src/comment-author-name/block.json b/packages/block-library/src/comment-author-name/block.json
index f3422faf0264df..1889d054c940e7 100644
--- a/packages/block-library/src/comment-author-name/block.json
+++ b/packages/block-library/src/comment-author-name/block.json
@@ -51,6 +51,19 @@
},
"interactivity": {
"clientNavigation": true
+ },
+ "__experimentalBorder": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true,
+ "__experimentalDefaultControls": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true
+ }
}
- }
+ },
+ "style": "wp-block-comment-author-name"
}
diff --git a/packages/block-library/src/comment-author-name/style.scss b/packages/block-library/src/comment-author-name/style.scss
new file mode 100644
index 00000000000000..1e8c5eeb154b05
--- /dev/null
+++ b/packages/block-library/src/comment-author-name/style.scss
@@ -0,0 +1,4 @@
+.wp-block-comment-author-name {
+ // This block has customizable padding, border-box makes that more predictable.
+ box-sizing: border-box;
+}
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 338753ae53fcbf..1070bedc8c3dbc 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -12,6 +12,7 @@
@import "./comment-template/style.scss";
@import "./comment-date/style.scss";
@import "./comment-content/style.scss";
+@import "./comment-author-name/style.scss";
@import "./cover/style.scss";
@import "./details/style.scss";
@import "./embed/style.scss";
From 0da4ce0b0343b2ec61d2718c20a9b2dc4de51fb3 Mon Sep 17 00:00:00 2001
From: George Mamadashvili
Date: Fri, 16 Aug 2024 12:34:02 +0400
Subject: [PATCH 117/126] Background Image: Remove unnecessary 'block-editor'
store subscription (#64568)
Co-authored-by: Mamaduka
Co-authored-by: andrewserong
Co-authored-by: ramonjd
---
.../src/components/global-styles/background-panel.js | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js
index c01db44792989e..6391f72a084875 100644
--- a/packages/block-editor/src/components/global-styles/background-panel.js
+++ b/packages/block-editor/src/components/global-styles/background-panel.js
@@ -288,10 +288,7 @@ function BackgroundImageControls( {
defaultValues,
} ) {
const [ isUploading, setIsUploading ] = useState( false );
- const mediaUpload = useSelect(
- ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
- []
- );
+ const { getSettings } = useSelect( blockEditorStore );
const { id, title, url } = style?.background?.backgroundImage || {
...inheritedValue?.background?.backgroundImage,
@@ -374,7 +371,7 @@ function BackgroundImageControls( {
);
return;
}
- mediaUpload( {
+ getSettings().mediaUpload( {
allowedTypes: [ IMAGE_BACKGROUND_TYPE ],
filesList,
onFileChange( [ image ] ) {
From 70d2020250ab71675ef5ce6330ca43e96200442c Mon Sep 17 00:00:00 2001
From: Jan Pfeil
Date: Fri, 16 Aug 2024 11:21:36 +0200
Subject: [PATCH 118/126] fix typo and link in static-dynamic-rendering.md
(#64449)
* fix typo and link in static-dynamic-rendering.md
* update link to render_callback
Unlinked contributors: janpfeil.
Co-authored-by: tyxla
---
docs/getting-started/fundamentals/static-dynamic-rendering.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/getting-started/fundamentals/static-dynamic-rendering.md b/docs/getting-started/fundamentals/static-dynamic-rendering.md
index 8d199f66cccd2a..dfb6a7123b44b3 100644
--- a/docs/getting-started/fundamentals/static-dynamic-rendering.md
+++ b/docs/getting-started/fundamentals/static-dynamic-rendering.md
@@ -61,7 +61,7 @@ Dynamic blocks, which we'll explore in the following section, can specify an ini
For a practical demonstration of how this works, refer to the [Building your first block](/docs/getting-started/tutorial.md) tutorial. Specifically, the [Adding static rendering](/docs/getting-started/tutorial.md#adding-static-rendering) section illustrates how a block can have both a saved HTML structure and dynamic rendering capabilities.
-WordPress provides mechanisms like the
render_block
are the
$render_callback
function to alter the saved HTML of a block before it appears on the front end. These tools offer developers the capability to customize block output dynamically, catering to complex and interactive user experiences.
+WordPress provides mechanisms like the
render_block
and the
render_callback
function to alter the saved HTML of a block before it appears on the front end. These tools offer developers the capability to customize block output dynamically, catering to complex and interactive user experiences.
Additional examples of WordPress blocks that use static rendering, meaning their output is fixed at the time of saving and doesn't rely on server-side processing, include:
From a00448ab9aa12ea99657e909a2a03cb01d041450 Mon Sep 17 00:00:00 2001
From: James Koster
Date: Fri, 16 Aug 2024 14:24:58 +0100
Subject: [PATCH 119/126] Update components radius (#64368)
Co-authored-by: jameskoster
Co-authored-by: tyxla
Co-authored-by: ciampo
Co-authored-by: jasmussen
---
packages/base-styles/_variables.scss | 2 +-
packages/components/CHANGELOG.md | 26 +++++++++++++++++++
.../styles/alignment-matrix-control-styles.ts | 4 +--
.../styles/angle-picker-control-styles.tsx | 4 +--
.../components/src/border-control/styles.ts | 2 +-
.../components/src/button-group/style.scss | 4 +--
packages/components/src/button/style.scss | 4 +--
.../src/circular-option-picker/style.scss | 4 +--
.../components/src/color-indicator/style.scss | 2 +-
.../components/src/color-palette/style.scss | 4 +--
.../src/custom-gradient-picker/style.scss | 2 +-
packages/components/src/drop-zone/style.scss | 2 +-
.../components/src/dropdown-menu-v2/styles.ts | 4 +--
.../styles/focal-point-picker-style.ts | 2 +-
packages/components/src/guide/style.scss | 1 -
packages/components/src/item-group/styles.ts | 2 +-
packages/components/src/modal/style.scss | 4 +--
.../components/src/palette-edit/styles.ts | 10 +++----
.../components/src/placeholder/style.scss | 2 +-
packages/components/src/popover/style.scss | 2 +-
.../components/src/progress-bar/styles.ts | 4 +--
.../components/src/radio-control/style.scss | 2 +-
packages/components/src/snackbar/style.scss | 2 +-
packages/components/src/tab-panel/style.scss | 2 +-
packages/components/src/text/styles.ts | 2 +-
.../test/__snapshots__/index.tsx.snap | 16 ++++++------
.../styles.ts | 4 +--
.../toggle-group-control/styles.ts | 2 +-
.../components/src/toolbar/toolbar/style.scss | 4 +--
packages/components/src/tooltip/style.scss | 2 +-
.../styles/unit-control-styles.ts | 2 +-
.../components/src/utils/config-values.js | 2 --
packages/components/src/utils/input/base.js | 2 +-
33 files changed, 78 insertions(+), 55 deletions(-)
diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss
index 0d3e139a7dd555..0f0c9e6d019ba3 100644
--- a/packages/base-styles/_variables.scss
+++ b/packages/base-styles/_variables.scss
@@ -47,7 +47,7 @@ $radius-x-small: 1px; // Applied to elements like buttons nested within primit
$radius-small: 2px; // Applied to most primitives.
$radius-medium: 4px; // Applied to containers with smaller padding.
$radius-large: 8px; // Applied to containers with larger padding.
-$radius-full: 9999px; // For lozenges.
+$radius-full: 9999px; // For pills.
$radius-round: 50%; // For circles and ovals.
/**
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index eb207643cdfcb2..1e6a96d2618d6f 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -27,6 +27,32 @@
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
- `FocalPointPicker`: Default to new 40px size ([#64456](https://github.com/WordPress/gutenberg/pull/64456)).
- `DropdownMenuV2`: adopt elevation scale ([#64432](https://github.com/WordPress/gutenberg/pull/64432)).
+- `AlignmentMatrixControl`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `AnglePickerControl`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `BorderControl`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `ButtonGroup`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Button`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `CircularOptionPicker`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `ColorIndicator`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `ColorPalette`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `CustomGradientPicker`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `DropZone`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `DropdownMenuV2`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `FocalPointPicker`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Guide`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Modal`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Placeholder`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Popover`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `ProgressBar`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `RadioControl`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Snackbar`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `TabPanel`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Text`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `ToggleGroupControl`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `ToolbarGroup`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Toolbar`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `Tooltip`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
+- `UnitControl`: Adopt radius scale ([#64368](https://github.com/WordPress/gutenberg/pull/64368)).
- `Popover`: allow `style` prop usage ([#64489](https://github.com/WordPress/gutenberg/pull/64489)).
- `ToolsPanel`: sets column-gap to 16px for ToolsPanel grid ([#64497](https://github.com/WordPress/gutenberg/pull/64497)).
diff --git a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts
index bdb015ec64c6a7..efbd23ab2be0af 100644
--- a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts
+++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.ts
@@ -7,7 +7,7 @@ import { css } from '@emotion/react';
/**
* Internal dependencies
*/
-import { COLORS } from '../../utils';
+import { COLORS, CONFIG } from '../../utils';
import type {
AlignmentMatrixControlProps,
AlignmentMatrixControlCellProps,
@@ -15,7 +15,7 @@ import type {
export const rootBase = () => {
return css`
- border-radius: 2px;
+ border-radius: ${ CONFIG.radiusMedium };
box-sizing: border-box;
direction: ltr;
display: grid;
diff --git a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.tsx b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.tsx
index 0141bd860d7df9..f57d60db0744b1 100644
--- a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.tsx
+++ b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.tsx
@@ -15,7 +15,7 @@ const CIRCLE_SIZE = 32;
const INNER_CIRCLE_SIZE = 6;
export const CircleRoot = styled.div`
- border-radius: 50%;
+ border-radius: ${ CONFIG.radiusRound };
border: ${ CONFIG.borderWidth } solid ${ COLORS.ui.border };
box-sizing: border-box;
cursor: grab;
@@ -41,7 +41,7 @@ export const CircleIndicatorWrapper = styled.div`
export const CircleIndicator = styled.div`
background: ${ COLORS.theme.accent };
- border-radius: 50%;
+ border-radius: ${ CONFIG.radiusRound };
box-sizing: border-box;
display: block;
left: 50%;
diff --git a/packages/components/src/border-control/styles.ts b/packages/components/src/border-control/styles.ts
index 28669ebf3ccc78..2c77a2d21465d6 100644
--- a/packages/components/src/border-control/styles.ts
+++ b/packages/components/src/border-control/styles.ts
@@ -99,7 +99,7 @@ export const colorIndicatorWrapper = (
const { style } = border || {};
return css`
- border-radius: 9999px;
+ border-radius: ${ CONFIG.radiusFull };
border: 2px solid transparent;
${ style ? colorIndicatorBorder( border ) : undefined }
width: ${ size === '__unstable-large' ? '24px' : '22px' };
diff --git a/packages/components/src/button-group/style.scss b/packages/components/src/button-group/style.scss
index 171722b409f693..96a9e8f458c84c 100644
--- a/packages/components/src/button-group/style.scss
+++ b/packages/components/src/button-group/style.scss
@@ -12,11 +12,11 @@
}
&:first-child {
- border-radius: $radius-block-ui 0 0 $radius-block-ui;
+ border-radius: $radius-small 0 0 $radius-small;
}
&:last-child {
- border-radius: 0 $radius-block-ui $radius-block-ui 0;
+ border-radius: 0 $radius-small $radius-small 0;
}
// The focused button should be elevated so the focus ring isn't cropped,
diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss
index 7d67dcc0748b85..444e4d397b3ef8 100644
--- a/packages/components/src/button/style.scss
+++ b/packages/components/src/button/style.scss
@@ -22,7 +22,7 @@
align-items: center;
box-sizing: border-box;
padding: 6px 12px;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
color: $components-color-foreground;
&.is-next-40px-default-size {
@@ -249,7 +249,7 @@
height: auto;
&:focus {
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
}
&:disabled,
diff --git a/packages/components/src/circular-option-picker/style.scss b/packages/components/src/circular-option-picker/style.scss
index 33ba6070dde799..74a380e0936be8 100644
--- a/packages/components/src/circular-option-picker/style.scss
+++ b/packages/components/src/circular-option-picker/style.scss
@@ -70,7 +70,7 @@ $color-palette-circle-spacing: 12px;
height: 100%;
width: 100%;
border: none;
- border-radius: 50%;
+ border-radius: $radius-round;
background: transparent;
box-shadow: inset 0 0 0 ($color-palette-circle-size * 0.5);
transition: 100ms box-shadow ease;
@@ -93,7 +93,7 @@ $color-palette-circle-spacing: 12px;
position: absolute;
left: 2px;
top: 2px;
- border-radius: 50%;
+ border-radius: $radius-round;
z-index: z-index(".components-circular-option-picker__option.is-pressed + svg");
pointer-events: none;
}
diff --git a/packages/components/src/color-indicator/style.scss b/packages/components/src/color-indicator/style.scss
index e70b8a09ca5bf5..4029b50340e62a 100644
--- a/packages/components/src/color-indicator/style.scss
+++ b/packages/components/src/color-indicator/style.scss
@@ -2,7 +2,7 @@
width: $grid-unit-50 * 0.5;
height: $grid-unit-50 * 0.5;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
- border-radius: 50%;
+ border-radius: $radius-round;
display: inline-block;
padding: 0;
background: $white linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%);
diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss
index 2d6bc4ddc1db3d..9d922a8130692a 100644
--- a/packages/components/src/color-palette/style.scss
+++ b/packages/components/src/color-palette/style.scss
@@ -14,7 +14,7 @@ $border-as-box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.2);
cursor: pointer;
// Show a thin outline in Windows high contrast mode.
outline: 1px solid transparent;
- border-radius: $radius-block-ui $radius-block-ui 0 0;
+ border-radius: $radius-medium $radius-medium 0 0;
box-shadow: $border-as-box-shadow;
&:focus {
@@ -46,7 +46,7 @@ $border-as-box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.2);
.components-color-palette__custom-color-text-wrapper {
padding: $grid-unit-15 $grid-unit-20;
- border-radius: 0 0 $radius-block-ui $radius-block-ui;
+ border-radius: 0 0 $radius-medium $radius-medium;
position: relative;
font-size: $default-font-size;
diff --git a/packages/components/src/custom-gradient-picker/style.scss b/packages/components/src/custom-gradient-picker/style.scss
index e7828127ff8d33..a8c6d6b872caf0 100644
--- a/packages/components/src/custom-gradient-picker/style.scss
+++ b/packages/components/src/custom-gradient-picker/style.scss
@@ -1,7 +1,7 @@
$components-custom-gradient-picker__padding: $grid-unit-20; // 48px container, 16px handles inside, that leaves 32px padding, half of which is 1å6.
.components-custom-gradient-picker__gradient-bar {
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
width: 100%;
height: $grid-unit-60;
position: relative;
diff --git a/packages/components/src/drop-zone/style.scss b/packages/components/src/drop-zone/style.scss
index 2793dc708cf03f..d3cd18a75b1f8f 100644
--- a/packages/components/src/drop-zone/style.scss
+++ b/packages/components/src/drop-zone/style.scss
@@ -7,7 +7,7 @@
z-index: z-index(".components-drop-zone");
visibility: hidden;
opacity: 0;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
&.is-active {
opacity: 1;
diff --git a/packages/components/src/dropdown-menu-v2/styles.ts b/packages/components/src/dropdown-menu-v2/styles.ts
index 950a549f8566a4..3260a83f45f1c1 100644
--- a/packages/components/src/dropdown-menu-v2/styles.ts
+++ b/packages/components/src/dropdown-menu-v2/styles.ts
@@ -87,7 +87,7 @@ export const DropdownMenu = styled( Ariakit.Menu )<
padding: ${ CONTENT_WRAPPER_PADDING };
background-color: ${ COLORS.ui.background };
- border-radius: 4px;
+ border-radius: ${ CONFIG.radiusMedium };
${ ( props ) => css`
box-shadow: ${ props.variant === 'toolbar'
? TOOLBAR_VARIANT_BOX_SHADOW
@@ -150,7 +150,7 @@ const baseItem = css`
line-height: 20px;
color: ${ COLORS.gray[ 900 ] };
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusSmall };
padding-block: ${ ITEM_PADDING_BLOCK };
padding-inline: ${ ITEM_PADDING_INLINE };
diff --git a/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts b/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts
index 71bf4b651b4ad7..fba3eda2fb0f29 100644
--- a/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts
+++ b/packages/components/src/focal-point-picker/styles/focal-point-picker-style.ts
@@ -22,7 +22,7 @@ export const MediaWrapper = styled.div`
export const MediaContainer = styled.div`
align-items: center;
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusSmall };
cursor: pointer;
display: inline-flex;
justify-content: center;
diff --git a/packages/components/src/guide/style.scss b/packages/components/src/guide/style.scss
index 76f7b03ac4aafc..073bfc06843075 100644
--- a/packages/components/src/guide/style.scss
+++ b/packages/components/src/guide/style.scss
@@ -9,7 +9,6 @@
.components-modal__content {
padding: 0;
margin-top: 0;
- border-radius: $radius-block-ui;
&::before {
content: none;
diff --git a/packages/components/src/item-group/styles.ts b/packages/components/src/item-group/styles.ts
index 166f225790ce27..66c6158f9af1a3 100644
--- a/packages/components/src/item-group/styles.ts
+++ b/packages/components/src/item-group/styles.ts
@@ -70,7 +70,7 @@ export const separated = css`
}
`;
-const borderRadius = CONFIG.controlBorderRadius;
+const borderRadius = CONFIG.radiusSmall;
export const spacedAround = css`
border-radius: ${ borderRadius };
diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss
index e563b29070bef2..c862363a0d3c81 100644
--- a/packages/components/src/modal/style.scss
+++ b/packages/components/src/modal/style.scss
@@ -21,7 +21,7 @@
width: 100%;
background: $white;
box-shadow: $shadow-modal;
- border-radius: $grid-unit-05 $grid-unit-05 0 0;
+ border-radius: $radius-large $radius-large 0 0;
overflow: hidden;
// Have the content element fill the vertical space yet not overflow.
display: flex;
@@ -32,7 +32,7 @@
// Show a centered modal on bigger screens.
@include break-small() {
- border-radius: $grid-unit-05;
+ border-radius: $radius-large;
margin: auto;
width: auto;
min-width: $modal-min-width;
diff --git a/packages/components/src/palette-edit/styles.ts b/packages/components/src/palette-edit/styles.ts
index aa4ed720b93bfe..ad918d8590cf23 100644
--- a/packages/components/src/palette-edit/styles.ts
+++ b/packages/components/src/palette-edit/styles.ts
@@ -31,7 +31,7 @@ export const IndicatorStyled = styled( ColorIndicator )`
export const NameInputControl = styled( InputControl )`
${ InputControlContainer } {
background: ${ COLORS.gray[ 100 ] };
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusXSmall };
${ Input }${ Input }${ Input }${ Input } {
height: ${ space( 8 ) };
}
@@ -85,8 +85,8 @@ export const PaletteItem = styled( View )`
outline-offset: 0;
}
- border-top-left-radius: ${ CONFIG.controlBorderRadius };
- border-top-right-radius: ${ CONFIG.controlBorderRadius };
+ border-top-left-radius: ${ CONFIG.radiusSmall };
+ border-top-right-radius: ${ CONFIG.radiusSmall };
& + & {
border-top-left-radius: 0;
@@ -94,8 +94,8 @@ export const PaletteItem = styled( View )`
}
&:last-child {
- border-bottom-left-radius: ${ CONFIG.controlBorderRadius };
- border-bottom-right-radius: ${ CONFIG.controlBorderRadius };
+ border-bottom-left-radius: ${ CONFIG.radiusSmall };
+ border-bottom-right-radius: ${ CONFIG.radiusSmall };
border-bottom-color: ${ CONFIG.surfaceBorderColor };
}
diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss
index e046ce1e3a427e..61090c81110a5c 100644
--- a/packages/components/src/placeholder/style.scss
+++ b/packages/components/src/placeholder/style.scss
@@ -19,7 +19,7 @@
// Block UI appearance.
- border-radius: $radius-block-ui;
+ border-radius: $radius-medium;
background-color: $white;
box-shadow: inset 0 0 0 $border-width $gray-900;
outline: 1px solid transparent; // Shown for Windows 10 High Contrast mode.
diff --git a/packages/components/src/popover/style.scss b/packages/components/src/popover/style.scss
index c7ff0510986bfa..f9f43870a6b927 100644
--- a/packages/components/src/popover/style.scss
+++ b/packages/components/src/popover/style.scss
@@ -23,7 +23,7 @@ $shadow-popover-border-top-only-alternate: 0 #{-$border-width} 0 $gray-900;
.components-popover__content {
background: $white;
box-shadow: $shadow-popover-border-default, $shadow-popover;
- border-radius: $radius-block-ui;
+ border-radius: $radius-medium;
box-sizing: border-box;
width: min-content;
diff --git a/packages/components/src/progress-bar/styles.ts b/packages/components/src/progress-bar/styles.ts
index 79b9103e73a1ed..585b9ab2620753 100644
--- a/packages/components/src/progress-bar/styles.ts
+++ b/packages/components/src/progress-bar/styles.ts
@@ -40,7 +40,7 @@ export const Track = styled.div`
${ COLORS.theme.foreground },
transparent 90%
);
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusFull };
// Windows high contrast mode.
outline: 2px solid transparent;
@@ -58,7 +58,7 @@ export const Indicator = styled.div< {
position: absolute;
top: 0;
height: 100%;
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusFull };
/* Text color at 90% opacity */
background-color: color-mix(
in srgb,
diff --git a/packages/components/src/radio-control/style.scss b/packages/components/src/radio-control/style.scss
index 7444ea1343b842..e9732558f2901e 100644
--- a/packages/components/src/radio-control/style.scss
+++ b/packages/components/src/radio-control/style.scss
@@ -32,7 +32,7 @@
&::before {
content: "";
- border-radius: 50%;
+ border-radius: $radius-round;
}
}
}
diff --git a/packages/components/src/snackbar/style.scss b/packages/components/src/snackbar/style.scss
index 0ba1774d67382f..5bea7076599b56 100644
--- a/packages/components/src/snackbar/style.scss
+++ b/packages/components/src/snackbar/style.scss
@@ -3,7 +3,7 @@
font-size: $default-font-size;
background: rgba($black, 0.85); // Emulates #1e1e1e closely.
backdrop-filter: blur($grid-unit-20) saturate(180%);
- border-radius: $radius-block-ui;
+ border-radius: $radius-medium;
box-shadow: $shadow-popover;
color: $white;
padding: $grid-unit-15 ($grid-unit-05 * 5);
diff --git a/packages/components/src/tab-panel/style.scss b/packages/components/src/tab-panel/style.scss
index 2855f8c2b06a01..ab73a7affaeed4 100644
--- a/packages/components/src/tab-panel/style.scss
+++ b/packages/components/src/tab-panel/style.scss
@@ -67,7 +67,7 @@
// Draw the indicator.
box-shadow: 0 0 0 0 transparent;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
// Animation
transition: all 0.1s linear;
diff --git a/packages/components/src/text/styles.ts b/packages/components/src/text/styles.ts
index c7d48552795938..e777ed4f0941de 100644
--- a/packages/components/src/text/styles.ts
+++ b/packages/components/src/text/styles.ts
@@ -35,7 +35,7 @@ export const muted = css`
export const highlighterText = css`
mark {
background: ${ COLORS.alert.yellow };
- border-radius: 2px;
+ border-radius: ${ CONFIG.radiusSmall };
box-shadow:
0 0 0 1px rgba( 0, 0, 0, 0.05 ) inset,
0 -1px 0 rgba( 0, 0, 0, 0.1 ) inset;
diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
index d055ea5fcc9838..e9b4f4ca22ab85 100644
--- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
+++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap
@@ -84,7 +84,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -152,7 +152,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
.emotion-15 {
background: #1e1e1e;
- border-radius: 2px;
+ border-radius: 1px;
position: absolute;
inset: 0;
z-index: 1;
@@ -171,7 +171,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -416,7 +416,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -636,7 +636,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -704,7 +704,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
.emotion-15 {
background: #1e1e1e;
- border-radius: 2px;
+ border-radius: 1px;
position: absolute;
inset: 0;
z-index: 1;
@@ -723,7 +723,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`]
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
@@ -962,7 +962,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio
appearance: none;
background: transparent;
border: none;
- border-radius: 2px;
+ border-radius: 1px;
color: #757575;
fill: currentColor;
cursor: pointer;
diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts
index 999a25df8bdd40..86efc5224077f4 100644
--- a/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts
+++ b/packages/components/src/toggle-group-control/toggle-group-control-option-base/styles.ts
@@ -37,7 +37,7 @@ export const buttonView = ( {
appearance: none;
background: transparent;
border: none;
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusXSmall };
color: ${ COLORS.gray[ 700 ] };
fill: currentColor;
cursor: pointer;
@@ -122,7 +122,7 @@ const isIconStyles = ( {
export const backdropView = css`
background: ${ COLORS.gray[ 900 ] };
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusXSmall };
position: absolute;
inset: 0;
z-index: 1;
diff --git a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts
index 7310024706e1de..8d01c150a45eaf 100644
--- a/packages/components/src/toggle-group-control/toggle-group-control/styles.ts
+++ b/packages/components/src/toggle-group-control/toggle-group-control/styles.ts
@@ -19,7 +19,7 @@ export const toggleGroupControl = ( {
} ) => css`
background: ${ COLORS.ui.background };
border: 1px solid transparent;
- border-radius: ${ CONFIG.controlBorderRadius };
+ border-radius: ${ CONFIG.radiusSmall };
display: inline-flex;
min-width: 0;
position: relative;
diff --git a/packages/components/src/toolbar/toolbar/style.scss b/packages/components/src/toolbar/toolbar/style.scss
index eccfc3bf705cbd..c0cabacb84c77e 100644
--- a/packages/components/src/toolbar/toolbar/style.scss
+++ b/packages/components/src/toolbar/toolbar/style.scss
@@ -1,7 +1,7 @@
.components-accessible-toolbar {
display: inline-flex;
border: $border-width solid $gray-900;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
flex-shrink: 0;
& > .components-toolbar-group:last-child {
@@ -47,7 +47,7 @@
content: "";
position: absolute;
display: block;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
height: $grid-unit-40;
// Position the focus rectangle.
diff --git a/packages/components/src/tooltip/style.scss b/packages/components/src/tooltip/style.scss
index feda6cfa81c887..eaac8b3ad1c7f6 100644
--- a/packages/components/src/tooltip/style.scss
+++ b/packages/components/src/tooltip/style.scss
@@ -1,7 +1,7 @@
.components-tooltip {
background: $black;
font-family: $default-font;
- border-radius: $radius-block-ui;
+ border-radius: $radius-small;
color: $gray-100;
text-align: center;
line-height: 1.4;
diff --git a/packages/components/src/unit-control/styles/unit-control-styles.ts b/packages/components/src/unit-control/styles/unit-control-styles.ts
index 321bfb8406569d..5f59771bd48a6a 100644
--- a/packages/components/src/unit-control/styles/unit-control-styles.ts
+++ b/packages/components/src/unit-control/styles/unit-control-styles.ts
@@ -135,7 +135,7 @@ export const UnitSelect = styled.select< SelectProps >`
&&& {
appearance: none;
background: transparent;
- border-radius: 2px;
+ border-radius: ${ CONFIG.radiusXSmall };
border: none;
display: block;
outline: none;
diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js
index 02c6f69544c2bf..0ad1b3294a926b 100644
--- a/packages/components/src/utils/config-values.js
+++ b/packages/components/src/utils/config-values.js
@@ -14,7 +14,6 @@ const CONTROL_PROPS = {
controlPaddingXLarge: `calc(${ CONTROL_PADDING_X } * 1.3334)`,
controlPaddingXSmall: `calc(${ CONTROL_PADDING_X } / 1.3334)`,
controlBackgroundColor: COLORS.white,
- controlBorderRadius: '2px',
controlBoxShadow: 'transparent',
controlBoxShadowFocus: `0 0 0 0.5px ${ COLORS.theme.accent }`,
controlDestructiveBorderColor: COLORS.alert.red,
@@ -48,7 +47,6 @@ export default Object.assign( {}, CONTROL_PROPS, TOGGLE_GROUP_CONTROL_PROPS, {
radiusLarge: '8px',
radiusFull: '9999px',
radiusRound: '50%',
- radiusBlockUi: '2px',
borderWidth: '1px',
borderWidthFocus: '1.5px',
borderWidthTab: '4px',
diff --git a/packages/components/src/utils/input/base.js b/packages/components/src/utils/input/base.js
index f03a1d9c77abbe..9eebd1c0bcea78 100644
--- a/packages/components/src/utils/input/base.js
+++ b/packages/components/src/utils/input/base.js
@@ -11,7 +11,7 @@ import { CONFIG } from '../';
export const inputStyleNeutral = css`
box-shadow: 0 0 0 transparent;
- border-radius: ${ CONFIG.radiusBlockUi };
+ border-radius: ${ CONFIG.radiusSmall };
border: ${ CONFIG.borderWidth } solid ${ COLORS.ui.border };
@media not ( prefers-reduced-motion ) {
From 42db13fbac9b738d1874896e77214a6cbbaf0927 Mon Sep 17 00:00:00 2001
From: Marco Ciampini
Date: Fri, 16 Aug 2024 17:34:39 +0200
Subject: [PATCH 120/126] Navigator: simplify backwards navigation APIs
(#63317)
* NavigatorProvider: make goBack an alias for goToParent
* NavigatorBackButton: deprecate and ignore `goToParent` prop
* NavigatorBackButton: always call `goBack`
* Navigator: deprecate `goToParent` method
* NavigatorToParentButton: deprecate the component, make it an alias for NavigatorGoBackButton
* Add missing JSDocs for Navigator types
* Update README
* More docs additions
* Fix tests by assuming that even an invalid HTML path starts with '/'
* Add emphasis on the need for `path` to start with `/`
* Add deprecation warnings
* Update unit tests
* Add back README for deprecated component
* Add deprecation warning for `goToParent` function
* CHANGELOG
* Add more docs
* Typos
* Remove entirely the `goToParent` prop on `useNavigatorBackButton`
This can be done because its usage was actually only internal to the component
* Add deprecated APIs tests
* Remove extra import (thank you autocomplete)
---
Co-authored-by: ciampo
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
.../navigator-back-button/component.tsx | 2 +-
.../navigator/navigator-back-button/hook.ts | 16 +-
.../navigator/navigator-provider/README.md | 74 ++++-----
.../navigator-provider/component.tsx | 20 ++-
.../src/navigator/navigator-screen/README.md | 14 +-
.../navigator-to-parent-button/README.md | 2 +
.../navigator-to-parent-button/component.tsx | 57 ++-----
.../components/src/navigator/test/index.tsx | 142 +++++++++++++++++-
packages/components/src/navigator/types.ts | 55 +++++--
10 files changed, 269 insertions(+), 114 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 1e6a96d2618d6f..f824bf8d2b2ddc 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -18,6 +18,7 @@
- `ToggleControl`
- `ToggleGroupControl`
- `TreeSelect`
+- Deprecate `NavigatorToParentButton` and `useNavigator().goToParent()` in favor of `NavigatorBackButton` and `useNavigator().goBack()` ([#63317](https://github.com/WordPress/gutenberg/pull/63317)).
### Enhancements
diff --git a/packages/components/src/navigator/navigator-back-button/component.tsx b/packages/components/src/navigator/navigator-back-button/component.tsx
index 71c5ac14cd00d9..88ed45b643a13d 100644
--- a/packages/components/src/navigator/navigator-back-button/component.tsx
+++ b/packages/components/src/navigator/navigator-back-button/component.tsx
@@ -48,7 +48,7 @@ function UnconnectedNavigatorBackButton(
*
* This is the child screen.
*
- * Go back
+ * Go back (to parent)
*
*
*
diff --git a/packages/components/src/navigator/navigator-back-button/hook.ts b/packages/components/src/navigator/navigator-back-button/hook.ts
index edf55be0f15f5b..d4447b5f40ad46 100644
--- a/packages/components/src/navigator/navigator-back-button/hook.ts
+++ b/packages/components/src/navigator/navigator-back-button/hook.ts
@@ -10,31 +10,27 @@ import type { WordPressComponentProps } from '../../context';
import { useContextSystem } from '../../context';
import Button from '../../button';
import useNavigator from '../use-navigator';
-import type { NavigatorBackButtonHookProps } from '../types';
+import type { NavigatorBackButtonProps } from '../types';
export function useNavigatorBackButton(
- props: WordPressComponentProps< NavigatorBackButtonHookProps, 'button' >
+ props: WordPressComponentProps< NavigatorBackButtonProps, 'button' >
) {
const {
onClick,
as = Button,
- goToParent: goToParentProp = false,
+
...otherProps
} = useContextSystem( props, 'NavigatorBackButton' );
- const { goBack, goToParent } = useNavigator();
+ const { goBack } = useNavigator();
const handleClick: React.MouseEventHandler< HTMLButtonElement > =
useCallback(
( e ) => {
e.preventDefault();
- if ( goToParentProp ) {
- goToParent();
- } else {
- goBack();
- }
+ goBack();
onClick?.( e );
},
- [ goToParentProp, goToParent, goBack, onClick ]
+ [ goBack, onClick ]
);
return {
diff --git a/packages/components/src/navigator/navigator-provider/README.md b/packages/components/src/navigator/navigator-provider/README.md
index 8be27a65101843..13745fae68a15d 100644
--- a/packages/components/src/navigator/navigator-provider/README.md
+++ b/packages/components/src/navigator/navigator-provider/README.md
@@ -10,38 +10,42 @@ The `NavigatorProvider` component allows rendering nested views/panels/menus (vi
```jsx
import {
- __experimentalNavigatorProvider as NavigatorProvider,
- __experimentalNavigatorScreen as NavigatorScreen,
- __experimentalNavigatorButton as NavigatorButton,
- __experimentalNavigatorToParentButton as NavigatorToParentButton,
+ __experimentalNavigatorProvider as NavigatorProvider,
+ __experimentalNavigatorScreen as NavigatorScreen,
+ __experimentalNavigatorButton as NavigatorButton,
+ __experimentalNavigatorBackButton as NavigatorBackButton,
} from '@wordpress/components';
const MyNavigation = () => (
-
-
- This is the home screen.
-
- Navigate to child screen.
-
-
-
-
- This is the child screen.
-
- Go back
-
-
-
+
+
+ This is the home screen.
+
+ Navigate to child screen.
+
+
+
+
+ This is the child screen.
+ Go back
+
+
);
```
+
**Important note**
-Parent/child navigation only works if the path you define are hierarchical, following a URL-like scheme where each path segment is separated by the `/` character.
+`Navigator` assumes that screens are organized hierarchically according to their `path`, which should follow a URL-like scheme where each path segment starts with and is separated by the `/` character.
+
+`Navigator` will treat "back" navigations as going to the parent screen — it is therefore responsibility of the consumer of the component to create the correct screen hierarchy.
+
For example:
-- `/` is the root of all paths. There should always be a screen with `path="/"`.
-- `/parent/child` is a child of `/parent`.
-- `/parent/child/grand-child` is a child of `/parent/child`.
-- `/parent/:param` is a child of `/parent` as well.
+
+- `/` is the root of all paths. There should always be a screen with `path="/"`.
+- `/parent/child` is a child of `/parent`.
+- `/parent/child/grand-child` is a child of `/parent/child`.
+- `/parent/:param` is a child of `/parent` as well.
+- if the current screen has a `path` with value `/parent/child/grand-child`, when going "back" `Navigator` will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) is found.
## Props
@@ -65,28 +69,26 @@ The `goTo` function allows navigating to a given path. The second argument can a
The available options are:
-- `focusTargetSelector`: `string`. An optional property used to specify the CSS selector used to restore focus on the matching element when navigating back.
-- `isBack`: `boolean`. An optional property used to specify whether the navigation should be considered as backwards (thus enabling focus restoration when possible, and causing the animation to be backwards too)
-
-### `goToParent`: `() => void;`
+- `focusTargetSelector`: `string`. An optional property used to specify the CSS selector used to restore focus on the matching element when navigating back;
+- `isBack`: `boolean`. An optional property used to specify whether the navigation should be considered as backwards (thus enabling focus restoration when possible, and causing the animation to be backwards too);
+- `skipFocus`: `boolean`. An optional property used to opt out of `Navigator`'s focus management, useful when the consumer of the component wants to manage focus themselves;
+- `replace`: `boolean`. An optional property used to cause the new location to replace the current location in the stack.
-The `goToParent` function allows navigating to the parent screen.
+### `goBack`: `( path: string, options: NavigateOptions ) => void`
-Parent/child navigation only works if the path you define are hierarchical (see note above).
+The `goBack` function allows navigating to the parent screen. Parent/child navigation only works if the paths you define are hierarchical (see note above).
When a match is not found, the function will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) are found.
-### `goBack`: `() => void`
-
-The `goBack` function allows navigating to the previous path.
+The available options are the same as for the `goTo` method, except for the `isBack` property, which is not available for the `goBack` method.
### `location`: `NavigatorLocation`
The `location` object represent the current location, and has a few properties:
-- `path`: `string`. The path associated to the location.
-- `isBack`: `boolean`. A flag that is `true` when the current location was reached by navigating backwards in the location stack.
-- `isInitial`: `boolean`. A flag that is `true` only for the first (root) location in the location stack.
+- `path`: `string`. The path associated to the location.
+- `isBack`: `boolean`. A flag that is `true` when the current location was reached by navigating backwards in the location history.
+- `isInitial`: `boolean`. A flag that is `true` only for the first (root) location in the location history.
### `params`: `Record< string, string | string[] >`
diff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx
index 15eb4d6bd3b1d3..78bc674c06fbd5 100644
--- a/packages/components/src/navigator/navigator-provider/component.tsx
+++ b/packages/components/src/navigator/navigator-provider/component.tsx
@@ -27,12 +27,12 @@ import type {
Screen,
NavigateToParentOptions,
} from '../types';
+import deprecated from '@wordpress/deprecated';
type MatchedPath = ReturnType< typeof patternMatch >;
type RouterAction =
| { type: 'add' | 'remove'; screen: Screen }
- | { type: 'goback' }
| { type: 'goto'; path: string; options?: NavigateOptions }
| { type: 'gotoparent'; options?: NavigateToParentOptions };
@@ -160,9 +160,6 @@ function routerReducer(
case 'remove':
screens = removeScreen( state, action.screen );
break;
- case 'goback':
- locationHistory = goBack( state );
- break;
case 'goto':
locationHistory = goTo( state, action.path, action.options );
break;
@@ -223,11 +220,20 @@ function UnconnectedNavigatorProvider(
// The methods are constant forever, create stable references to them.
const methods = useMemo(
() => ( {
- goBack: () => dispatch( { type: 'goback' } ),
+ // Note: calling goBack calls `goToParent` internally, as it was established
+ // that `goBack` should behave like `goToParent`, and `goToParent` should
+ // be marked as deprecated.
+ goBack: ( options: NavigateToParentOptions | undefined ) =>
+ dispatch( { type: 'gotoparent', options } ),
goTo: ( path: string, options?: NavigateOptions ) =>
dispatch( { type: 'goto', path, options } ),
- goToParent: ( options: NavigateToParentOptions | undefined ) =>
- dispatch( { type: 'gotoparent', options } ),
+ goToParent: ( options: NavigateToParentOptions | undefined ) => {
+ deprecated( `wp.components.useNavigator().goToParent`, {
+ since: '6.7',
+ alternative: 'wp.components.useNavigator().goBack',
+ } );
+ dispatch( { type: 'gotoparent', options } );
+ },
addScreen: ( screen: Screen ) =>
dispatch( { type: 'add', screen } ),
removeScreen: ( screen: Screen ) =>
diff --git a/packages/components/src/navigator/navigator-screen/README.md b/packages/components/src/navigator/navigator-screen/README.md
index 5ba5af44fe8c1a..583da461cd3c27 100644
--- a/packages/components/src/navigator/navigator-screen/README.md
+++ b/packages/components/src/navigator/navigator-screen/README.md
@@ -16,6 +16,18 @@ The component accepts the following props:
### `path`: `string`
-The screen's path, matched against the current path stored in the navigator.
+The screen"s path, matched against the current path stored in the navigator.
+
+`Navigator` assumes that screens are organized hierarchically according to their `path`, which should follow a URL-like scheme where each path segment starts with and is separated by the `/` character.
+
+`Navigator` will treat "back" navigations as going to the parent screen — it is therefore responsibility of the consumer of the component to create the correct screen hierarchy.
+
+For example:
+
+- `/` is the root of all paths. There should always be a screen with `path="/"`.
+- `/parent/child` is a child of `/parent`.
+- `/parent/child/grand-child` is a child of `/parent/child`.
+- `/parent/:param` is a child of `/parent` as well.
+- if the current screen has a `path` with value `/parent/child/grand-child`, when going "back" `Navigator` will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) is found.
- Required: Yes
diff --git a/packages/components/src/navigator/navigator-to-parent-button/README.md b/packages/components/src/navigator/navigator-to-parent-button/README.md
index 62dacc3dfa4ea5..0100ea9b8d2e1f 100644
--- a/packages/components/src/navigator/navigator-to-parent-button/README.md
+++ b/packages/components/src/navigator/navigator-to-parent-button/README.md
@@ -4,6 +4,8 @@
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
+This component is deprecated. Please use the [`NavigatorBackButton`](/packages/components/src/navigator/navigator-back-button/README.md) component instead.
+
The `NavigatorToParentButton` component can be used to navigate to a screen and should be used in combination with the [`NavigatorProvider`](/packages/components/src/navigator/navigator-provider/README.md), the [`NavigatorScreen`](/packages/components/src/navigator/navigator-screen/README.md) and the [`NavigatorButton`](/packages/components/src/navigator/navigator-button/README.md) components (or the `useNavigator` hook).
## Usage
diff --git a/packages/components/src/navigator/navigator-to-parent-button/component.tsx b/packages/components/src/navigator/navigator-to-parent-button/component.tsx
index e73a3619f3d494..400498b1fc96ca 100644
--- a/packages/components/src/navigator/navigator-to-parent-button/component.tsx
+++ b/packages/components/src/navigator/navigator-to-parent-button/component.tsx
@@ -1,62 +1,33 @@
/**
- * External dependencies
+ * WordPress dependencies
*/
-import type { ForwardedRef } from 'react';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
+import { NavigatorBackButton } from '../navigator-back-button';
import type { WordPressComponentProps } from '../../context';
import { contextConnect } from '../../context';
-import { View } from '../../view';
-import { useNavigatorBackButton } from '../navigator-back-button/hook';
-import type { NavigatorToParentButtonProps } from '../types';
+import type { NavigatorBackButtonProps } from '../types';
function UnconnectedNavigatorToParentButton(
- props: WordPressComponentProps< NavigatorToParentButtonProps, 'button' >,
- forwardedRef: ForwardedRef< any >
+ props: WordPressComponentProps< NavigatorBackButtonProps, 'button' >,
+ forwardedRef: React.ForwardedRef< any >
) {
- const navigatorToParentButtonProps = useNavigatorBackButton( {
- ...props,
- goToParent: true,
+ deprecated( 'wp.components.NavigatorToParentButton', {
+ since: '6.7',
+ alternative: 'wp.components.NavigatorBackButton',
} );
- return ;
+ return ;
}
-/*
- * The `NavigatorToParentButton` component can be used to navigate to a screen and
- * should be used in combination with the `NavigatorProvider`, the
- * `NavigatorScreen` and the `NavigatorButton` components (or the `useNavigator`
- * hook).
- *
- * @example
- * ```jsx
- * import {
- * __experimentalNavigatorProvider as NavigatorProvider,
- * __experimentalNavigatorScreen as NavigatorScreen,
- * __experimentalNavigatorButton as NavigatorButton,
- * __experimentalNavigatorToParentButton as NavigatorToParentButton,
- * } from '@wordpress/components';
- *
- * const MyNavigation = () => (
- *
- *
- * This is the home screen.
- *
- * Navigate to child screen.
- *
- *
+/**
+ * _Note: this component is deprecated. Please use the `NavigatorBackButton`
+ * component instead._
*
- *
- * This is the child screen.
- *
- * Go to parent
- *
- *
- *
- * );
- * ```
+ * @deprecated
*/
export const NavigatorToParentButton = contextConnect(
UnconnectedNavigatorToParentButton,
diff --git a/packages/components/src/navigator/test/index.tsx b/packages/components/src/navigator/test/index.tsx
index b83bd70d9d7444..9b9b257ea09681 100644
--- a/packages/components/src/navigator/test/index.tsx
+++ b/packages/components/src/navigator/test/index.tsx
@@ -25,8 +25,8 @@ import {
import type { NavigateOptions } from '../types';
const INVALID_HTML_ATTRIBUTE = {
- raw: ' "\'><=invalid_path',
- escaped: " "'><=invalid_path",
+ raw: '/ "\'><=invalid_path',
+ escaped: "/ "'><=invalid_path",
};
const PATHS = {
@@ -165,6 +165,27 @@ function CustomNavigatorToParentButton( {
);
}
+function CustomNavigatorToParentButtonAlternative( {
+ onClick,
+ children,
+}: {
+ children: React.ReactNode;
+ onClick?: CustomTestOnClickHandler;
+} ) {
+ const { goToParent } = useNavigator();
+ return (
+ {
+ goToParent();
+ // Used to spy on the values passed to `navigator.goBack`.
+ onClick?.( { type: 'goToParent' } );
+ } }
+ >
+ { children }
+
+ );
+}
+
const ProductScreen = ( {
onBackButtonClick,
}: {
@@ -344,20 +365,20 @@ const MyHierarchicalNavigation = ( {
>
{ BUTTON_TEXT.toNestedScreen }
-
{ BUTTON_TEXT.back }
-
+
{ SCREEN_TEXT.nested }
-
{ BUTTON_TEXT.back }
-
+
{
+ return (
+ <>
+
+
+ { SCREEN_TEXT.home }
+ { /*
+ * A button useful to test focus restoration. This button is the first
+ * tabbable item in the screen, but should not receive focus when
+ * navigating to screen as a result of a backwards navigation.
+ */ }
+ First tabbable home screen button
+
+ { BUTTON_TEXT.toChildScreen }
+
+
+
+
+ { SCREEN_TEXT.child }
+ { /*
+ * A button useful to test focus restoration. This button is the first
+ * tabbable item in the screen, but should not receive focus when
+ * navigating to screen as a result of a backwards navigation.
+ */ }
+ First tabbable child screen button
+
+ { BUTTON_TEXT.toNestedScreen }
+
+
+ { BUTTON_TEXT.back }
+
+
+
+
+ { SCREEN_TEXT.nested }
+
+ { BUTTON_TEXT.back }
+
+
+
+ >
+ );
+};
+
const getScreen = ( screenKey: keyof typeof SCREEN_TEXT ) =>
screen.getByText( SCREEN_TEXT[ screenKey ] );
const queryScreen = ( screenKey: keyof typeof SCREEN_TEXT ) =>
@@ -769,4 +850,53 @@ describe( 'Navigator', () => {
).toHaveFocus();
} );
} );
+
+ describe( 'deprecated APIs', () => {
+ it( 'should log a deprecation notice when using the NavigatorToParentButton component', async () => {
+ const user = userEvent.setup();
+
+ render( );
+
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
+
+ // Navigate back to home screen.
+ // The first tabbable element receives focus, since focus restoration
+ // it not possible (there was no forward navigation).
+ await user.click( getNavigationButton( 'back' ) );
+ expect( getScreen( 'home' ) ).toBeInTheDocument();
+ expect(
+ screen.getByRole( 'button', {
+ name: 'First tabbable home screen button',
+ } )
+ ).toHaveFocus();
+
+ // Rendering `NavigatorToParentButton` logs a deprecation notice
+ expect( console ).toHaveWarnedWith(
+ 'wp.components.NavigatorToParentButton is deprecated since version 6.7. Please use wp.components.NavigatorBackButton instead.'
+ );
+ } );
+
+ it( 'should log a deprecation notice when using the useNavigator().goToParent() function', async () => {
+ const user = userEvent.setup();
+
+ render( );
+
+ expect( getScreen( 'nested' ) ).toBeInTheDocument();
+
+ // Navigate back to child screen using the back button.
+ // The first tabbable element receives focus, since focus restoration
+ // it not possible (there was no forward navigation).
+ await user.click( getNavigationButton( 'back' ) );
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
+ expect(
+ screen.getByRole( 'button', {
+ name: 'First tabbable child screen button',
+ } )
+ ).toHaveFocus();
+
+ expect( console ).toHaveWarnedWith(
+ 'wp.components.useNavigator().goToParent is deprecated since version 6.7. Please use wp.components.useNavigator().goBack instead.'
+ );
+ } );
+ } );
} );
diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts
index 557f8074fd42e2..c45762d558af2d 100644
--- a/packages/components/src/navigator/types.ts
+++ b/packages/components/src/navigator/types.ts
@@ -11,26 +11,70 @@ import type { ButtonAsButtonProps } from '../button/types';
export type MatchParams = Record< string, string | string[] >;
export type NavigateOptions = {
+ /**
+ * Specify the CSS selector used to restore focus on an given element when
+ * navigating back. When not provided, the component will attempt to restore
+ * focus on the element that originated the forward navigation.
+ */
focusTargetSelector?: string;
+ /**
+ * Whether the navigation is a backwards navigation. This enables focus
+ * restoration (when possible), and causes the animation to be backwards.
+ */
isBack?: boolean;
+ /**
+ * Opt out of focus management. Useful when the consumer of the component
+ * wants to manage focus themselves.
+ */
skipFocus?: boolean;
+ /**
+ * Whether the navigation should replace the current location in the stack.
+ */
replace?: boolean;
};
export type NavigateToParentOptions = Omit< NavigateOptions, 'isBack' >;
export type NavigatorLocation = NavigateOptions & {
+ /**
+ * Whether the current location is the initial one (ie. first in the stack).
+ */
isInitial?: boolean;
+ /**
+ * The path associated to the location.
+ */
path?: string;
+ /**
+ * Whether focus was already restored for this location (in case of
+ * backwards navigation).
+ */
hasRestoredFocus?: boolean;
};
// Returned by the `useNavigator` hook.
export type Navigator = {
+ /**
+ * The current location.
+ */
location: NavigatorLocation;
+ /**
+ * Params associated with the current location
+ */
params: MatchParams;
+ /**
+ * Navigate to a new location.
+ */
goTo: ( path: string, options?: NavigateOptions ) => void;
- goBack: () => void;
+ /**
+ * Go back to the parent location (ie. "/some/path" will navigate back
+ * to "/some")
+ */
+ goBack: ( options?: NavigateToParentOptions ) => void;
+ /**
+ * _Note: This function is deprecated. Please use `goBack` instead._
+ * @deprecated
+ * @ignore
+ */
goToParent: ( options?: NavigateToParentOptions ) => void;
};
@@ -64,15 +108,6 @@ export type NavigatorScreenProps = {
export type NavigatorBackButtonProps = ButtonAsButtonProps;
-export type NavigatorBackButtonHookProps = NavigatorBackButtonProps & {
- /**
- * Whether we should navigate to the parent screen.
- *
- * @default 'false'
- */
- goToParent?: boolean;
-};
-
export type NavigatorToParentButtonProps = NavigatorBackButtonProps;
export type NavigatorButtonProps = NavigatorBackButtonProps & {
From 65da2128fb798638717bc27d55823590afc3cd47 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Sat, 17 Aug 2024 01:56:12 +0900
Subject: [PATCH 121/126] CustomSelectControl: Add lint rule for 40px size prop
usage (#64559)
* Fix in DateFormatPicker
* Make explicit in FontAppearanceControl
* Add lint rule
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
Co-authored-by: ciampo
---
.eslintrc.js | 1 +
.../block-editor/src/components/date-format-picker/index.js | 1 +
.../src/components/font-appearance-control/index.js | 3 +++
3 files changed, 5 insertions(+)
diff --git a/.eslintrc.js b/.eslintrc.js
index 2cdbb5c056cb14..42bb0accd72f1a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -313,6 +313,7 @@ module.exports = {
...[
'BorderBoxControl',
'BorderControl',
+ 'CustomSelectControl',
'DimensionControl',
'FontSizePicker',
'ToggleGroupControl',
diff --git a/packages/block-editor/src/components/date-format-picker/index.js b/packages/block-editor/src/components/date-format-picker/index.js
index 63c977b111e01c..edefd6249f1aae 100644
--- a/packages/block-editor/src/components/date-format-picker/index.js
+++ b/packages/block-editor/src/components/date-format-picker/index.js
@@ -129,6 +129,7 @@ function NonDefaultControls( { format, onChange } ) {
return (
{
*/
export default function FontAppearanceControl( props ) {
const {
+ /** Start opting into the larger default height that will become the default size in a future version. */
+ __next40pxDefaultSize = false,
onChange,
hasFontStyles = true,
hasFontWeights = true,
@@ -150,6 +152,7 @@ export default function FontAppearanceControl( props ) {
Date: Sat, 17 Aug 2024 02:38:24 +0900
Subject: [PATCH 122/126] NumberControl: Add lint rule for 40px size prop usage
(#64561)
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: ciampo
Co-authored-by: tyxla
---
.eslintrc.js | 1 +
.../src/components/line-height-control/README.md | 7 +++++++
.../src/components/line-height-control/index.js | 3 +++
3 files changed, 11 insertions(+)
diff --git a/.eslintrc.js b/.eslintrc.js
index 42bb0accd72f1a..e21f7d6e6d7806 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -316,6 +316,7 @@ module.exports = {
'CustomSelectControl',
'DimensionControl',
'FontSizePicker',
+ 'NumberControl',
'ToggleGroupControl',
].map( ( componentName ) => ( {
// Falsy `__next40pxDefaultSize` without a non-default `size` prop.
diff --git a/packages/block-editor/src/components/line-height-control/README.md b/packages/block-editor/src/components/line-height-control/README.md
index dafad9145022b9..89bcc69622367f 100644
--- a/packages/block-editor/src/components/line-height-control/README.md
+++ b/packages/block-editor/src/components/line-height-control/README.md
@@ -36,6 +36,13 @@ The value of the line height.
A callback function that handles the application of the line height value.
+#### `__next40pxDefaultSize`
+
+- **Type:** `boolean`
+- **Default:** `false`
+
+Start opting into the larger default height that will become the default size in a future version.
+
## Related components
Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree.
diff --git a/packages/block-editor/src/components/line-height-control/index.js b/packages/block-editor/src/components/line-height-control/index.js
index d605aea3d2ef18..b2c99c03f87840 100644
--- a/packages/block-editor/src/components/line-height-control/index.js
+++ b/packages/block-editor/src/components/line-height-control/index.js
@@ -16,6 +16,8 @@ import {
} from './utils';
const LineHeightControl = ( {
+ /** Start opting into the larger default height that will become the default size in a future version. */
+ __next40pxDefaultSize = false,
value: lineHeight,
onChange,
__unstableInputWidth = '60px',
@@ -91,6 +93,7 @@ const LineHeightControl = ( {
Date: Sat, 17 Aug 2024 04:01:35 +0900
Subject: [PATCH 123/126] ComboboxControl: Add lint rule for 40px size prop
usage (#64560)
* Fix in Author block
* Fix in Avatar block
* Add lint rule
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: ciampo
Co-authored-by: tyxla
---
.eslintrc.js | 1 +
.../block-library/src/avatar/user-control.js | 1 +
packages/block-library/src/editor.scss | 1 +
.../block-library/src/post-author/block.json | 1 +
.../block-library/src/post-author/edit.js | 125 ++++++++++--------
.../block-library/src/post-author/editor.scss | 7 +
6 files changed, 78 insertions(+), 58 deletions(-)
create mode 100644 packages/block-library/src/post-author/editor.scss
diff --git a/.eslintrc.js b/.eslintrc.js
index e21f7d6e6d7806..f6b24dcb15017f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -313,6 +313,7 @@ module.exports = {
...[
'BorderBoxControl',
'BorderControl',
+ 'ComboboxControl',
'CustomSelectControl',
'DimensionControl',
'FontSizePicker',
diff --git a/packages/block-library/src/avatar/user-control.js b/packages/block-library/src/avatar/user-control.js
index 598c05011eaed7..235202ffec90d7 100644
--- a/packages/block-library/src/avatar/user-control.js
+++ b/packages/block-library/src/avatar/user-control.js
@@ -33,6 +33,7 @@ function UserControl( { value, onChange } ) {
return (
- { showAuthorControl &&
- ( ( showCombobox && (
-
- ) ) || (
+
+ { showAuthorControl &&
+ ( ( showCombobox && (
+
+ ) ) || (
+
+ ) ) }
+
+ setAttributes( { showAvatar: ! showAvatar } )
+ }
+ />
+ { showAvatar && (
{
+ setAttributes( {
+ avatarSize: Number( size ),
+ } );
+ } }
/>
- ) ) }
-
- setAttributes( { showAvatar: ! showAvatar } )
- }
- />
- { showAvatar && (
- {
- setAttributes( {
- avatarSize: Number( size ),
- } );
- } }
+ label={ __( 'Show bio' ) }
+ checked={ showBio }
+ onChange={ () =>
+ setAttributes( { showBio: ! showBio } )
+ }
/>
- ) }
-
- setAttributes( { showBio: ! showBio } )
- }
- />
- setAttributes( { isLink: ! isLink } ) }
- />
- { isLink && (
- setAttributes( {
- linkTarget: value ? '_blank' : '_self',
- } )
+ label={ __( 'Link author name to author page' ) }
+ checked={ isLink }
+ onChange={ () =>
+ setAttributes( { isLink: ! isLink } )
}
- checked={ linkTarget === '_blank' }
/>
- ) }
+ { isLink && (
+
+ setAttributes( {
+ linkTarget: value ? '_blank' : '_self',
+ } )
+ }
+ checked={ linkTarget === '_blank' }
+ />
+ ) }
+
diff --git a/packages/block-library/src/post-author/editor.scss b/packages/block-library/src/post-author/editor.scss
new file mode 100644
index 00000000000000..f6464893138cff
--- /dev/null
+++ b/packages/block-library/src/post-author/editor.scss
@@ -0,0 +1,7 @@
+.wp-block-post-author__inspector-settings {
+ // Counteract the margin added by the block inspector.
+ .components-base-control,
+ .components-base-control:last-child {
+ margin-bottom: 0;
+ }
+}
From c459a7bebec8d8c7db8af2a0be6517a02d573b3c Mon Sep 17 00:00:00 2001
From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com>
Date: Sat, 17 Aug 2024 00:39:54 +0530
Subject: [PATCH 124/126] Feat: Button groups in Typography tools should use
ToggleGroupControl (#64529)
* Update the letterCase typogrpahy panel to use toggleGroupOption component
* Remove unecessary style for segmented text control and match height with components
* Remove SegmentedTextControl component and use toggleControl
* Reduce the diff change in the PR
Co-authored-by: hbhalodia
Co-authored-by: t-hamano
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: ciampo
Co-authored-by: tyxla
---
.../segmented-text-control/index.js | 63 -------------------
.../segmented-text-control/style.scss | 15 -----
.../text-alignment-control/index.js | 28 ++++++---
.../text-decoration-control/index.js | 28 ++++++---
.../text-transform-control/index.js | 28 ++++++---
.../components/writing-mode-control/index.js | 28 ++++++---
packages/block-editor/src/style.scss | 1 -
7 files changed, 80 insertions(+), 111 deletions(-)
delete mode 100644 packages/block-editor/src/components/segmented-text-control/index.js
delete mode 100644 packages/block-editor/src/components/segmented-text-control/style.scss
diff --git a/packages/block-editor/src/components/segmented-text-control/index.js b/packages/block-editor/src/components/segmented-text-control/index.js
deleted file mode 100644
index 28f049bfb40ec9..00000000000000
--- a/packages/block-editor/src/components/segmented-text-control/index.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * External dependencies
- */
-import clsx from 'clsx';
-
-/**
- * WordPress dependencies
- */
-import { BaseControl, Button } from '@wordpress/components';
-
-/**
- * @typedef {Object} Option
- * @property {string} label The label of the option.
- * @property {string} value The value of the option.
- * @property {string} icon The icon of the option.
- */
-
-/**
- * Control to facilitate selecting a text style from a set of options.
- *
- * @param {Object} props Component props.
- * @param {string} props.label A label for the option.
- * @param {string} props.value Currently selected value.
- * @param {Function} props.onChange Callback to handle onChange.
- * @param {Option[]} props.options Array of options to display.
- * @param {string} props.className Additional class name to apply.
- *
- * @return {Element} Element to render.
- */
-export default function SegmentedTextControl( {
- label,
- value,
- options,
- onChange,
- className,
-} ) {
- return (
-
-
- { label }
-
-
- { options.map( ( option ) => {
- return (
- onChange( option.value ) }
- />
- );
- } ) }
-
-
- );
-}
diff --git a/packages/block-editor/src/components/segmented-text-control/style.scss b/packages/block-editor/src/components/segmented-text-control/style.scss
deleted file mode 100644
index 7a4a3bbea7cb33..00000000000000
--- a/packages/block-editor/src/components/segmented-text-control/style.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.block-editor-segmented-text-control {
- border: 0;
- margin: 0;
- padding: 0;
-
- .block-editor-segmented-text-control__buttons {
- // 4px of padding makes the row 40px high, same as an input.
- padding: $grid-unit-05 0;
- display: flex;
- }
-
- .components-button.has-icon {
- margin-right: $grid-unit-05;
- }
-}
diff --git a/packages/block-editor/src/components/text-alignment-control/index.js b/packages/block-editor/src/components/text-alignment-control/index.js
index 88a6fe274ea09b..6eeaad784db0f0 100644
--- a/packages/block-editor/src/components/text-alignment-control/index.js
+++ b/packages/block-editor/src/components/text-alignment-control/index.js
@@ -14,11 +14,10 @@ import {
alignJustify,
} from '@wordpress/icons';
import { useMemo } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import SegmentedTextControl from '../segmented-text-control';
+import {
+ __experimentalToggleGroupControl as ToggleGroupControl,
+ __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
+} from '@wordpress/components';
const TEXT_ALIGNMENT_OPTIONS = [
{
@@ -75,9 +74,11 @@ export default function TextAlignmentControl( {
}
return (
- {
onChange( newValue === value ? undefined : newValue );
} }
- />
+ >
+ { validOptions.map( ( option ) => {
+ return (
+
+ );
+ } ) }
+
);
}
diff --git a/packages/block-editor/src/components/text-decoration-control/index.js b/packages/block-editor/src/components/text-decoration-control/index.js
index d06632afdbb3ca..720f3d2d9558eb 100644
--- a/packages/block-editor/src/components/text-decoration-control/index.js
+++ b/packages/block-editor/src/components/text-decoration-control/index.js
@@ -8,11 +8,10 @@ import clsx from 'clsx';
*/
import { reset, formatStrikethrough, formatUnderline } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import SegmentedTextControl from '../segmented-text-control';
+import {
+ __experimentalToggleGroupControl as ToggleGroupControl,
+ __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
+} from '@wordpress/components';
const TEXT_DECORATIONS = [
{
@@ -48,9 +47,11 @@ export default function TextDecorationControl( {
className,
} ) {
return (
- {
onChange( newValue === value ? undefined : newValue );
} }
- />
+ >
+ { TEXT_DECORATIONS.map( ( option ) => {
+ return (
+
+ );
+ } ) }
+
);
}
diff --git a/packages/block-editor/src/components/text-transform-control/index.js b/packages/block-editor/src/components/text-transform-control/index.js
index f448a55ed946c3..a3183e630c328a 100644
--- a/packages/block-editor/src/components/text-transform-control/index.js
+++ b/packages/block-editor/src/components/text-transform-control/index.js
@@ -13,11 +13,10 @@ import {
formatLowercase,
formatUppercase,
} from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
-import SegmentedTextControl from '../segmented-text-control';
+import {
+ __experimentalToggleGroupControl as ToggleGroupControl,
+ __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
+} from '@wordpress/components';
const TEXT_TRANSFORMS = [
{
@@ -54,9 +53,11 @@ const TEXT_TRANSFORMS = [
*/
export default function TextTransformControl( { className, value, onChange } ) {
return (
- {
onChange( newValue === value ? undefined : newValue );
} }
- />
+ >
+ { TEXT_TRANSFORMS.map( ( option ) => {
+ return (
+
+ );
+ } ) }
+
);
}
diff --git a/packages/block-editor/src/components/writing-mode-control/index.js b/packages/block-editor/src/components/writing-mode-control/index.js
index 7732f54b1569a7..bffccfd579c6b0 100644
--- a/packages/block-editor/src/components/writing-mode-control/index.js
+++ b/packages/block-editor/src/components/writing-mode-control/index.js
@@ -8,11 +8,10 @@ import clsx from 'clsx';
*/
import { __, isRTL } from '@wordpress/i18n';
import { textHorizontal, textVertical } from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
-import SegmentedTextControl from '../segmented-text-control';
+import {
+ __experimentalToggleGroupControl as ToggleGroupControl,
+ __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
+} from '@wordpress/components';
const WRITING_MODES = [
{
@@ -39,14 +38,27 @@ const WRITING_MODES = [
*/
export default function WritingModeControl( { className, value, onChange } ) {
return (
- {
onChange( newValue === value ? undefined : newValue );
} }
- />
+ >
+ { WRITING_MODES.map( ( option ) => {
+ return (
+
+ );
+ } ) }
+
);
}
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index 47b87bb50918df..feaabbbda94426 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -40,7 +40,6 @@
@import "./components/multi-selection-inspector/style.scss";
@import "./components/responsive-block-control/style.scss";
@import "./components/rich-text/style.scss";
-@import "./components/segmented-text-control/style.scss";
@import "./components/skip-to-selected-block/style.scss";
@import "./components/tabbed-sidebar/style.scss";
@import "./components/tool-selector/style.scss";
From 68dd5ce54901a2d5f8bf25ba170d603701426ad4 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Sat, 17 Aug 2024 04:33:24 +0900
Subject: [PATCH 125/126] RangeControl: Add lint rule for 40px size prop usage
(#64558)
* Fix in Query Loop block
* Fix in Excerpt block
* Fix in Grid block
* Fix in SpacingSizesControl
* Fix in Image block Zoom control
* Fix in HeightControl
* Fix in BorderRadiusControl
* Add lint rule
* Fixup in HeightControl
* Address in ColorPicker
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.eslintrc.js | 1 +
.../components/border-radius-control/index.js | 1 +
.../border-radius-control/style.scss | 10 -------
.../src/components/height-control/index.js | 1 +
.../components/image-editor/zoom-dropdown.js | 26 ++++++++++++-------
.../input-controls/spacing-input-control.js | 2 ++
.../spacing-sizes-control/style.scss | 10 -------
packages/block-editor/src/layouts/grid.js | 2 ++
.../block-library/src/post-excerpt/edit.js | 1 +
.../src/query-pagination-numbers/edit.js | 1 +
.../src/color-picker/input-with-slider.tsx | 1 +
11 files changed, 27 insertions(+), 29 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index f6b24dcb15017f..5a939aeb9173b7 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -318,6 +318,7 @@ module.exports = {
'DimensionControl',
'FontSizePicker',
'NumberControl',
+ 'RangeControl',
'ToggleGroupControl',
].map( ( componentName ) => ( {
// Falsy `__next40pxDefaultSize` without a non-default `size` prop.
diff --git a/packages/block-editor/src/components/border-radius-control/index.js b/packages/block-editor/src/components/border-radius-control/index.js
index 4c614084a7e200..cab9b87b3b29c0 100644
--- a/packages/block-editor/src/components/border-radius-control/index.js
+++ b/packages/block-editor/src/components/border-radius-control/index.js
@@ -104,6 +104,7 @@ export default function BorderRadiusControl( { onChange, values } ) {
units={ units }
/>
div {
- height: 40px;
- display: flex;
- align-items: center;
- }
- }
-
- > span {
- flex: 0 0 auto;
}
}
diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js
index 71753a67beb021..5d42e217776d6a 100644
--- a/packages/block-editor/src/components/height-control/index.js
+++ b/packages/block-editor/src/components/height-control/index.js
@@ -164,6 +164,7 @@ export default function HeightControl( {
) }
renderContent={ () => (
-
+
+
+
) }
/>
);
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 5cdfe47c452b23..d00feed704d17a 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
@@ -249,6 +249,7 @@ export default function SpacingInputControl( {
} }
/>
.components-base-control__field {
- /* Fixes RangeControl contents when the outer wrapper is flex */
- flex: 1;
- }
}
.components-range-control__mark {
diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js
index c574ad0ae43d5d..7ab5c7ebbc8c3d 100644
--- a/packages/block-editor/src/layouts/grid.js
+++ b/packages/block-editor/src/layouts/grid.js
@@ -264,6 +264,7 @@ function GridLayoutMinimumWidthControl( { layout, onChange } ) {
) : (
diff --git a/packages/block-library/src/post-excerpt/edit.js b/packages/block-library/src/post-excerpt/edit.js
index 45d0a79a411d61..05aaf543b59196 100644
--- a/packages/block-library/src/post-excerpt/edit.js
+++ b/packages/block-library/src/post-excerpt/edit.js
@@ -231,6 +231,7 @@ export default function PostExcerptEditor( {
}
/>
Date: Sat, 17 Aug 2024 05:51:32 +0800
Subject: [PATCH 126/126] CustomSelectControl: improve props type inferring
(#64412)
* CustomSelectControl: improve props type inferring
* chore: update changelog
* chore: updates
Co-authored-by: meteorlxy
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
packages/components/CHANGELOG.md | 4 +
.../src/custom-select-control/test/index.tsx | 125 ++++++++++++++++++
.../src/custom-select-control/types.ts | 6 +-
3 files changed, 132 insertions(+), 3 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index f824bf8d2b2ddc..f1b6ba3341de60 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -67,6 +67,10 @@
- `Composite` v2: add focus-related props to `Composite`and`Composite.Item` subcomponents ([#64450](https://github.com/WordPress/gutenberg/pull/64450)).
- `Composite` v2: add `Context` subcomponent ([#64493](https://github.com/WordPress/gutenberg/pull/64493)).
+### Internal
+
+- `CustomSelectControl`: Improve type inferring ([#64412](https://github.com/WordPress/gutenberg/pull/64412)).
+
## 28.5.0 (2024-08-07)
### Bug Fixes
diff --git a/packages/components/src/custom-select-control/test/index.tsx b/packages/components/src/custom-select-control/test/index.tsx
index fdbe8d72a48dec..b2ac5c19c6ab3f 100644
--- a/packages/components/src/custom-select-control/test/index.tsx
+++ b/packages/components/src/custom-select-control/test/index.tsx
@@ -689,3 +689,128 @@ describe.each( [
} );
} );
} );
+
+describe( 'Type checking', () => {
+ // eslint-disable-next-line jest/expect-expect
+ it( 'should infer the value type from available `options`, but not the `value` or `onChange` prop', () => {
+ const options = [
+ {
+ key: 'narrow',
+ name: 'Narrow',
+ },
+ {
+ key: 'value',
+ name: 'Value',
+ },
+ ];
+ const optionsReadOnly = [
+ {
+ key: 'narrow',
+ name: 'Narrow',
+ },
+ {
+ key: 'value',
+ name: 'Value',
+ },
+ ] as const;
+
+ const onChange = (): void => {};
+
+ ;
+
+ ;
+
+ ;
+
+ void
+ }
+ />;
+
+ ;
+
+ ;
+
+ ;
+
+ void
+ }
+ />;
+ } );
+} );
diff --git a/packages/components/src/custom-select-control/types.ts b/packages/components/src/custom-select-control/types.ts
index e37ba349a2b843..0cbc2388e79638 100644
--- a/packages/components/src/custom-select-control/types.ts
+++ b/packages/components/src/custom-select-control/types.ts
@@ -55,7 +55,7 @@ export type CustomSelectProps< T extends CustomSelectOption > = {
* Function called with the control's internal state changes. The `selectedItem`
* property contains the next selected item.
*/
- onChange?: ( newValue: CustomSelectChangeObject< T > ) => void;
+ onChange?: ( newValue: CustomSelectChangeObject< NoInfer< T > > ) => void;
/**
* A handler for `blur` events on the trigger button.
*
@@ -83,7 +83,7 @@ export type CustomSelectProps< T extends CustomSelectOption > = {
/**
* The list of options that can be chosen from.
*/
- options: Array< T >;
+ options: ReadonlyArray< T >;
/**
* The size of the control.
*
@@ -93,7 +93,7 @@ export type CustomSelectProps< T extends CustomSelectOption > = {
/**
* Can be used to externally control the value of the control.
*/
- value?: T;
+ value?: NoInfer< T >;
/**
* Use the `showSelectedHint` property instead.
* @deprecated