diff --git a/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js b/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js
index 792f6d0bb2012..46fd83c92d91f 100644
--- a/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js
+++ b/packages/block-editor/src/components/block-switcher/pattern-transformations-menu.js
@@ -3,7 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
-import { useInstanceId } from '@wordpress/compose';
+import { useInstanceId, useViewportMatch } from '@wordpress/compose';
import { chevronRight } from '@wordpress/icons';
import {
@@ -34,6 +34,7 @@ function PatternTransformationsMenu( {
} ) {
const [ showTransforms, setShowTransforms ] = useState( false );
const patterns = useTransformedPatterns( statePatterns, blocks );
+
if ( ! patterns.length ) {
return null;
}
@@ -60,21 +61,22 @@ function PatternTransformationsMenu( {
}
function PreviewPatternsPopover( { patterns, onSelect } ) {
+ const isMobile = useViewportMatch( 'medium', '<' );
+
return (
-
-
+
);
}
diff --git a/packages/block-editor/src/components/block-switcher/preview-block-popover.js b/packages/block-editor/src/components/block-switcher/preview-block-popover.js
index 78e29f4ad09fb..f978ed43af1da 100644
--- a/packages/block-editor/src/components/block-switcher/preview-block-popover.js
+++ b/packages/block-editor/src/components/block-switcher/preview-block-popover.js
@@ -3,6 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { Popover } from '@wordpress/components';
+import { useViewportMatch } from '@wordpress/compose';
/**
* Internal dependencies
@@ -10,22 +11,27 @@ import { Popover } from '@wordpress/components';
import BlockPreview from '../block-preview';
export default function PreviewBlockPopover( { blocks } ) {
+ const isMobile = useViewportMatch( 'medium', '<' );
+
+ if ( isMobile ) {
+ return null;
+ }
+
return (
-
-
-
-
-
- { __( 'Preview' ) }
-
-
+
+
+
+
+ { __( 'Preview' ) }
-
-
+
+
+
);
}
diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss
index 5eaba08bf94ae..afcb576bd8db4 100644
--- a/packages/block-editor/src/components/block-switcher/style.scss
+++ b/packages/block-editor/src/components/block-switcher/style.scss
@@ -84,27 +84,18 @@
min-width: 300px;
}
-.block-editor-block-switcher__popover__preview__parent {
- .block-editor-block-switcher__popover__preview__container {
- position: absolute;
- top: -$grid-unit-15;
- left: calc(100% + #{$grid-unit-20});
- }
+.block-editor-block-switcher__popover-preview-container {
+ left: 0;
+ position: absolute;
+ top: -$border-width;
+ width: 100%;
+ bottom: 0;
+ pointer-events: none;
}
-.block-editor-block-switcher__preview__popover {
- display: none;
+.block-editor-block-switcher__popover-preview {
overflow: hidden;
- // Position correctly. Needs specificity.
- &.components-popover {
- margin-top: $grid-unit-15 - $border-width;
- }
-
- @include break-medium() {
- display: block;
- }
-
.components-popover__content {
width: 300px;
border: $border-width solid $gray-900;
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 18ecc26b002d0..25ff5887eb5e6 100644
--- a/packages/block-editor/src/components/global-styles/background-panel.js
+++ b/packages/block-editor/src/components/global-styles/background-panel.js
@@ -451,6 +451,9 @@ function BackgroundSizeControls( {
const positionValue =
style?.background?.backgroundPosition ||
inheritedValue?.background?.backgroundPosition;
+ const attachmentValue =
+ style?.background?.backgroundAttachment ||
+ inheritedValue?.background?.backgroundAttachment;
/*
* An `undefined` value is replaced with any supplied
@@ -546,8 +549,17 @@ function BackgroundSizeControls( {
)
);
+ const toggleScrollWithPage = () =>
+ onChange(
+ setImmutably(
+ style,
+ [ 'background', 'backgroundAttachment' ],
+ attachmentValue === 'fixed' ? 'scroll' : 'fixed'
+ )
+ );
+
return (
-
+
+
{
- const { user: config, setUserConfig } = useContext( GlobalStylesContext );
+ const { user, setUserConfig } = useContext( GlobalStylesContext );
+ const config = {
+ settings: user.settings,
+ styles: user.styles,
+ };
const canReset = !! config && ! fastDeepEqual( config, EMPTY_CONFIG );
return [
canReset,
diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss
index f57bc79074153..bbd1964b81cea 100644
--- a/packages/block-editor/src/components/global-styles/style.scss
+++ b/packages/block-editor/src/components/global-styles/style.scss
@@ -103,8 +103,13 @@
.block-editor-global-styles-background-panel__image-tools-panel-item {
border: 1px solid $gray-300;
border-radius: 2px;
+
// Full width. ToolsPanel lays out children in a grid.
grid-column: 1 / -1;
+
+ // Ensure the dropzone is positioned to the size of the item.
+ position: relative;
+
// Since there is no option to skip rendering the drag'n'drop icon in drop
// zone, we hide it for now.
.components-drop-zone__content-icon {
diff --git a/packages/block-editor/src/components/global-styles/test/typography-utils.js b/packages/block-editor/src/components/global-styles/test/typography-utils.js
index 115de8cdf2563..41a7d6b5e37b8 100644
--- a/packages/block-editor/src/components/global-styles/test/typography-utils.js
+++ b/packages/block-editor/src/components/global-styles/test/typography-utils.js
@@ -6,6 +6,8 @@ import {
getFluidTypographyOptionsFromSettings,
getMergedFontFamiliesAndFontFamilyFaces,
findNearestFontWeight,
+ findNearestFontStyle,
+ findNearestStyleAndWeight,
} from '../typography-utils';
describe( 'typography utils', () => {
@@ -951,6 +953,329 @@ describe( 'typography utils', () => {
);
} );
+ describe( 'findNearestFontStyle', () => {
+ [
+ {
+ message:
+ 'should return empty string when newFontStyleValue is `undefined`',
+ availableFontStyles: undefined,
+ newFontStyleValue: undefined,
+ expected: '',
+ },
+ {
+ message:
+ 'should return newFontStyleValue value when availableFontStyles is empty',
+ availableFontStyles: [],
+ newFontStyleValue: 'italic',
+ expected: 'italic',
+ },
+ {
+ message:
+ 'should return empty string if there is no new font style available',
+ availableFontStyles: [ { name: 'Normal', value: 'normal' } ],
+ newFontStyleValue: 'italic',
+ expected: '',
+ },
+ {
+ message:
+ 'should return empty string if the new font style is invalid',
+ availableFontStyles: [
+ { name: 'Regular', value: 'normal' },
+ { name: 'Italic', value: 'italic' },
+ ],
+ newFontStyleValue: 'not-valid',
+ expected: '',
+ },
+ {
+ message: 'should return italic if oblique is not available',
+ availableFontStyles: [
+ { name: 'Regular', value: 'normal' },
+ { name: 'Italic', value: 'italic' },
+ ],
+ newFontStyleValue: 'oblique',
+ expected: 'italic',
+ },
+ {
+ message: 'should return normal if normal is available',
+ availableFontStyles: [
+ { name: 'Regular', value: 'normal' },
+ { name: 'Italic', value: 'italic' },
+ ],
+ newFontStyleValue: 'normal',
+ expected: 'normal',
+ },
+ ].forEach(
+ ( {
+ message,
+ availableFontStyles,
+ newFontStyleValue,
+ expected,
+ } ) => {
+ it( `${ message }`, () => {
+ expect(
+ findNearestFontStyle(
+ availableFontStyles,
+ newFontStyleValue
+ )
+ ).toEqual( expected );
+ } );
+ }
+ );
+ } );
+
+ describe( 'findNearestStyleAndWeight', () => {
+ [
+ {
+ message: 'should return empty object when all values are empty',
+ fontFamilyFaces: [],
+ fontStyle: undefined,
+ fontWeight: undefined,
+ expected: {},
+ },
+ {
+ message:
+ 'should return original fontStyle and fontWeight when fontFamilyFaces is empty',
+ fontFamilyFaces: [],
+ fontStyle: 'italic',
+ fontWeight: '700',
+ expected: {
+ nearestFontStyle: 'italic',
+ nearestFontWeight: '700',
+ },
+ },
+ {
+ message:
+ 'should return undefined values if both fontStyle and fontWeight are not available',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'ABeeZee',
+ fontStyle: 'italic',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/esDT31xSG-6AGleN2tCkkJUCGpG-GQ.woff2',
+ ],
+ },
+ ],
+ fontStyle: undefined,
+ fontWeight: undefined,
+ expected: {
+ nearestFontStyle: undefined,
+ nearestFontWeight: undefined,
+ },
+ },
+ {
+ message:
+ 'should return nearest fontStyle and fontWeight for normal/400',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'italic',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Italic.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '700',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2',
+ ],
+ },
+ ],
+ fontStyle: 'normal',
+ fontWeight: '400',
+ expected: {
+ nearestFontStyle: 'normal',
+ nearestFontWeight: '400',
+ },
+ },
+ {
+ message:
+ 'should return nearest fontStyle and fontWeight for normal/100',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'italic',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Italic.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '700',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2',
+ ],
+ },
+ ],
+ fontStyle: 'normal',
+ fontWeight: '100',
+ expected: {
+ nearestFontStyle: 'normal',
+ nearestFontWeight: '400',
+ },
+ },
+ {
+ message:
+ 'should return nearest fontStyle and fontWeight for italic/900',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'italic',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Italic.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '700',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2',
+ ],
+ },
+ ],
+ fontStyle: 'italic',
+ fontWeight: '900',
+ expected: {
+ nearestFontStyle: 'italic',
+ nearestFontWeight: '700',
+ },
+ },
+ {
+ message:
+ 'should return nearest fontStyle and fontWeight for oblique/600',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'italic',
+ fontWeight: '700',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2',
+ ],
+ },
+ ],
+ fontStyle: 'oblique',
+ fontWeight: '600',
+ expected: {
+ nearestFontStyle: 'italic',
+ nearestFontWeight: '700',
+ },
+ },
+ {
+ message:
+ 'should return nearest fontStyle and fontWeight for 300 font weight and empty font style',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'italic',
+ fontWeight: '700',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2',
+ ],
+ },
+ ],
+ fontStyle: undefined,
+ fontWeight: '300',
+ expected: {
+ nearestFontStyle: 'normal',
+ nearestFontWeight: '400',
+ },
+ },
+ {
+ message:
+ 'should return nearest fontStyle and fontWeight for oblique font style and empty font weight',
+ fontFamilyFaces: [
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'normal',
+ fontWeight: '400',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.woff2',
+ ],
+ },
+ {
+ fontFamily: 'IBM Plex Mono',
+ fontStyle: 'italic',
+ fontWeight: '700',
+ src: [
+ 'file:./assets/fonts/ibm-plex-mono/IBMPlexMono-Bold.woff2',
+ ],
+ },
+ ],
+ fontStyle: 'oblique',
+ fontWeight: undefined,
+ expected: {
+ nearestFontStyle: 'italic',
+ nearestFontWeight: '400',
+ },
+ },
+ ].forEach(
+ ( {
+ message,
+ fontFamilyFaces,
+ fontStyle,
+ fontWeight,
+ expected,
+ } ) => {
+ it( `${ message }`, () => {
+ expect(
+ findNearestStyleAndWeight(
+ fontFamilyFaces,
+ fontStyle,
+ fontWeight
+ )
+ ).toEqual( expected );
+ } );
+ }
+ );
+ } );
+
describe( 'typography utils', () => {
[
{
diff --git a/packages/block-editor/src/components/global-styles/typography-panel.js b/packages/block-editor/src/components/global-styles/typography-panel.js
index c497ea4683391..f6a389a5bc96d 100644
--- a/packages/block-editor/src/components/global-styles/typography-panel.js
+++ b/packages/block-editor/src/components/global-styles/typography-panel.js
@@ -25,9 +25,8 @@ import { getValueFromVariable, useToolsPanelDropdownMenuProps } from './utils';
import { setImmutably } from '../../utils/object';
import {
getMergedFontFamiliesAndFontFamilyFaces,
- findNearestFontWeight,
+ findNearestStyleAndWeight,
} from './typography-utils';
-import { getFontStylesAndWeights } from '../../utils/get-font-styles-and-weights';
const MIN_TEXT_COLUMNS = 1;
const MAX_TEXT_COLUMNS = 6;
@@ -236,57 +235,50 @@ export default function TypographyPanel( {
const hasFontWeights = settings?.typography?.fontWeight;
const fontStyle = decodeValue( inheritedValue?.typography?.fontStyle );
const fontWeight = decodeValue( inheritedValue?.typography?.fontWeight );
- const setFontAppearance = ( {
- fontStyle: newFontStyle,
- fontWeight: newFontWeight,
- } ) => {
- onChange( {
- ...value,
- typography: {
- ...value?.typography,
- fontStyle: newFontStyle || undefined,
- fontWeight: newFontWeight || undefined,
- },
- } );
- };
+ const { nearestFontStyle, nearestFontWeight } = findNearestStyleAndWeight(
+ fontFamilyFaces,
+ fontStyle,
+ fontWeight
+ );
+ const setFontAppearance = useCallback(
+ ( { fontStyle: newFontStyle, fontWeight: newFontWeight } ) => {
+ // Only update the font style and weight if they have changed.
+ if ( newFontStyle !== fontStyle || newFontWeight !== fontWeight ) {
+ onChange( {
+ ...value,
+ typography: {
+ ...value?.typography,
+ fontStyle: newFontStyle || undefined,
+ fontWeight: newFontWeight || undefined,
+ },
+ } );
+ }
+ },
+ [ fontStyle, fontWeight, onChange, value ]
+ );
const hasFontAppearance = () =>
!! value?.typography?.fontStyle || !! value?.typography?.fontWeight;
- const resetFontAppearance = () => {
+ const resetFontAppearance = useCallback( () => {
setFontAppearance( {} );
- };
+ }, [ setFontAppearance ] );
- // Check if previous font style and weight values are available in the new font family
+ // Check if previous font style and weight values are available in the new font family.
useEffect( () => {
- const { fontStyles, fontWeights, isSystemFont } =
- getFontStylesAndWeights( fontFamilyFaces );
- const hasFontStyle = fontStyles?.some(
- ( { value: fs } ) => fs === fontStyle
- );
- const hasFontWeight = fontWeights?.some(
- ( { value: fw } ) => fw === fontWeight
- );
-
- // Try to set nearest available font weight
- if ( ! hasFontWeight && fontWeight ) {
+ if ( nearestFontStyle && nearestFontWeight ) {
setFontAppearance( {
- fontStyle,
- fontWeight: findNearestFontWeight( fontWeights, fontWeight ),
+ fontStyle: nearestFontStyle,
+ fontWeight: nearestFontWeight,
} );
- }
-
- // Set the same weight and style values if the font family is a system font or if both are the same
- if ( isSystemFont || ( hasFontStyle && hasFontWeight ) ) {
- setFontAppearance( {
- fontStyle,
- fontWeight,
- } );
- }
-
- // Reset font appearance if the font family does not have the selected font style
- if ( ! hasFontStyle ) {
+ } else {
+ // Reset font appearance if there are no available styles or weights.
resetFontAppearance();
}
- }, [ fontFamily ] );
+ }, [
+ nearestFontStyle,
+ nearestFontWeight,
+ resetFontAppearance,
+ setFontAppearance,
+ ] );
// Line Height
const hasLineHeightEnabled = useHasLineHeightControl( settings );
diff --git a/packages/block-editor/src/components/global-styles/typography-utils.js b/packages/block-editor/src/components/global-styles/typography-utils.js
index 0d4c1b29b8b66..59ff04bf21ebc 100644
--- a/packages/block-editor/src/components/global-styles/typography-utils.js
+++ b/packages/block-editor/src/components/global-styles/typography-utils.js
@@ -11,6 +11,7 @@ import {
getComputedFluidTypographyValue,
getTypographyValueAndUnit,
} from '../font-sizes/fluid-utils';
+import { getFontStylesAndWeights } from '../../utils/get-font-styles-and-weights';
/**
* @typedef {Object} FluidPreset
@@ -127,9 +128,9 @@ export function getFluidTypographyOptionsFromSettings( settings ) {
* Returns an object of merged font families and the font faces from the selected font family
* based on the theme.json settings object and the currently selected font family.
*
- * @param {Object} settings Theme.json settings
- * @param {string} selectedFontFamily Decoded font family string
- * @return {Object} Merged font families and font faces from the selected font family
+ * @param {Object} settings Theme.json settings.
+ * @param {string} selectedFontFamily Decoded font family string.
+ * @return {Object} Merged font families and font faces from the selected font family.
*/
export function getMergedFontFamiliesAndFontFamilyFaces(
settings,
@@ -153,11 +154,10 @@ export function getMergedFontFamiliesAndFontFamilyFaces(
* Returns the nearest font weight value from the available font weight list based on the new font weight.
* The nearest font weight is the one with the smallest difference from the new font weight.
*
- * @param {Array} availableFontWeights Array of available font weights
- * @param {string} newFontWeightValue New font weight value
- * @return {string} Nearest font weight
+ * @param {Array} availableFontWeights Array of available font weights.
+ * @param {string} newFontWeightValue New font weight value.
+ * @return {string} Nearest font weight.
*/
-
export function findNearestFontWeight(
availableFontWeights,
newFontWeightValue
@@ -185,3 +185,99 @@ export function findNearestFontWeight(
return nearestFontWeight;
}
+
+/**
+ * Returns the nearest font style based on the new font style.
+ * Defaults to an empty string if the new font style is not valid or available.
+ *
+ * @param {Array} availableFontStyles Array of available font weights.
+ * @param {string} newFontStyleValue New font style value.
+ * @return {string} Nearest font style or an empty string.
+ */
+export function findNearestFontStyle( availableFontStyles, newFontStyleValue ) {
+ if ( typeof newFontStyleValue !== 'string' || ! newFontStyleValue ) {
+ return '';
+ }
+
+ const validStyles = [ 'normal', 'italic', 'oblique' ];
+ if ( ! validStyles.includes( newFontStyleValue ) ) {
+ return '';
+ }
+
+ if (
+ ! availableFontStyles ||
+ availableFontStyles.length === 0 ||
+ availableFontStyles.find(
+ ( style ) => style.value === newFontStyleValue
+ )
+ ) {
+ return newFontStyleValue;
+ }
+
+ if (
+ newFontStyleValue === 'oblique' &&
+ ! availableFontStyles.find( ( style ) => style.value === 'oblique' )
+ ) {
+ return 'italic';
+ }
+
+ return '';
+}
+
+/**
+ * Returns the nearest font style and weight based on the available font family faces and the new font style and weight.
+ *
+ * @param {Array} fontFamilyFaces Array of available font family faces.
+ * @param {string} fontStyle New font style. Defaults to previous value.
+ * @param {string} fontWeight New font weight. Defaults to previous value.
+ * @return {Object} Nearest font style and font weight.
+ */
+export function findNearestStyleAndWeight(
+ fontFamilyFaces,
+ fontStyle,
+ fontWeight
+) {
+ let nearestFontStyle = fontStyle;
+ let nearestFontWeight = fontWeight;
+
+ const { fontStyles, fontWeights, combinedStyleAndWeightOptions } =
+ getFontStylesAndWeights( fontFamilyFaces );
+
+ // Check if the new font style and weight are available in the font family faces.
+ const hasFontStyle = fontStyles?.some(
+ ( { value: fs } ) => fs === fontStyle
+ );
+ const hasFontWeight = fontWeights?.some(
+ ( { value: fw } ) => fw === fontWeight
+ );
+
+ if ( ! hasFontStyle ) {
+ /*
+ * Default to italic if oblique is not available.
+ * Or find the nearest font style based on the nearest font weight.
+ */
+ nearestFontStyle = fontStyle
+ ? findNearestFontStyle( fontStyles, fontStyle )
+ : combinedStyleAndWeightOptions?.find(
+ ( option ) =>
+ option.style.fontWeight ===
+ findNearestFontWeight( fontWeights, fontWeight )
+ )?.style?.fontStyle;
+ }
+
+ if ( ! hasFontWeight ) {
+ /*
+ * Find the nearest font weight based on available weights.
+ * Or find the nearest font weight based on the nearest font style.
+ */
+ nearestFontWeight = fontWeight
+ ? findNearestFontWeight( fontWeights, fontWeight )
+ : combinedStyleAndWeightOptions?.find(
+ ( option ) =>
+ option.style.fontStyle ===
+ ( nearestFontStyle || fontStyle )
+ )?.style?.fontWeight;
+ }
+
+ return { nearestFontStyle, nearestFontWeight };
+}
diff --git a/packages/block-editor/src/hooks/grid-visualizer.js b/packages/block-editor/src/hooks/grid-visualizer.js
index ffad5edd9842c..44102208c4d1d 100644
--- a/packages/block-editor/src/hooks/grid-visualizer.js
+++ b/packages/block-editor/src/hooks/grid-visualizer.js
@@ -39,7 +39,7 @@ function GridTools( { clientId, layout } ) {
const addGridVisualizerToBlockEdit = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
if ( props.attributes.layout?.type !== 'grid' ) {
- return ;
+ return ;
}
return (
@@ -48,7 +48,7 @@ const addGridVisualizerToBlockEdit = createHigherOrderComponent(
clientId={ props.clientId }
layout={ props.attributes.layout }
/>
-
+
>
);
},
diff --git a/packages/block-editor/src/layouts/constrained.js b/packages/block-editor/src/layouts/constrained.js
index 03d2c642d02bd..2e671e8e53975 100644
--- a/packages/block-editor/src/layouts/constrained.js
+++ b/packages/block-editor/src/layouts/constrained.js
@@ -234,15 +234,23 @@ export default {
const paddingValues = getCSSRules( style );
paddingValues.forEach( ( rule ) => {
if ( rule.key === 'paddingRight' ) {
+ // Add unit if 0, to avoid calc(0 * -1) which is invalid.
+ const paddingRightValue =
+ rule.value === '0' ? '0px' : rule.value;
+
output += `
${ appendSelectors( selector, '> .alignfull' ) } {
- margin-right: calc(${ rule.value } * -1);
+ margin-right: calc(${ paddingRightValue } * -1);
}
`;
} else if ( rule.key === 'paddingLeft' ) {
+ // Add unit if 0, to avoid calc(0 * -1) which is invalid.
+ const paddingLeftValue =
+ rule.value === '0' ? '0px' : rule.value;
+
output += `
${ appendSelectors( selector, '> .alignfull' ) } {
- margin-left: calc(${ rule.value } * -1);
+ margin-left: calc(${ paddingLeftValue } * -1);
}
`;
}
diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js
index 5fdeba99dcef6..86c9483c98b34 100644
--- a/packages/block-library/src/block/edit.js
+++ b/packages/block-library/src/block/edit.js
@@ -156,7 +156,11 @@ function ReusableBlockEdit( {
getBlockEditingMode: _getBlockEditingMode,
} = select( blockEditorStore );
const { getBlockBindingsSource } = unlock( select( blocksStore ) );
- const canEdit = canUser( 'update', 'blocks', ref );
+ const canEdit = canUser( 'update', {
+ kind: 'postType',
+ name: 'wp_block',
+ id: ref,
+ } );
// For editing link to the site editor if the theme and user permissions support it.
return {
diff --git a/packages/block-library/src/buttons/block.json b/packages/block-library/src/buttons/block.json
index 015290a4c7cc5..220f6ef8fd1e2 100644
--- a/packages/block-library/src/buttons/block.json
+++ b/packages/block-library/src/buttons/block.json
@@ -13,8 +13,16 @@
"align": [ "wide", "full" ],
"html": false,
"__experimentalExposeControlsToChildren": true,
+ "color": {
+ "gradients": true,
+ "text": false,
+ "__experimentalDefaultControls": {
+ "background": true
+ }
+ },
"spacing": {
"blockGap": true,
+ "padding": true,
"margin": [ "top", "bottom" ],
"__experimentalDefaultControls": {
"blockGap": true
@@ -33,6 +41,18 @@
"fontSize": true
}
},
+ "__experimentalBorder": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true,
+ "__experimentalDefaultControls": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true
+ }
+ },
"layout": {
"allowSwitching": false,
"allowInheriting": false,
diff --git a/packages/block-library/src/categories/block.json b/packages/block-library/src/categories/block.json
index 820ac8945f50f..7f74befa3b681 100644
--- a/packages/block-library/src/categories/block.json
+++ b/packages/block-library/src/categories/block.json
@@ -26,6 +26,14 @@
"showEmpty": {
"type": "boolean",
"default": false
+ },
+ "label": {
+ "type": "string",
+ "__experimentalRole": "content"
+ },
+ "showLabel": {
+ "type": "boolean",
+ "default": true
}
},
"supports": {
diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js
index 0c4df48d7c3dc..48d505eeb75ae 100644
--- a/packages/block-library/src/categories/edit.js
+++ b/packages/block-library/src/categories/edit.js
@@ -14,7 +14,11 @@ import {
VisuallyHidden,
} from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
-import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
+import {
+ InspectorControls,
+ useBlockProps,
+ RichText,
+} from '@wordpress/block-editor';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';
import { pin } from '@wordpress/icons';
@@ -27,6 +31,8 @@ export default function CategoriesEdit( {
showPostCounts,
showOnlyTopLevel,
showEmpty,
+ label,
+ showLabel,
},
setAttributes,
className,
@@ -92,9 +98,22 @@ export default function CategoriesEdit( {
const categoriesList = getCategoriesList( parentId );
return (
<>
-
- { __( 'Categories' ) }
-
+ { showLabel ? (
+
+ setAttributes( { label: html } )
+ }
+ />
+ ) : (
+
+ { label ? label : __( 'Categories' ) }
+
+ ) }
';
+ $show_label = empty( $attributes['showLabel'] ) ? ' screen-reader-text' : '';
+ $default_label = __( 'Categories' );
+ $label_text = ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label;
+ $wrapper_markup = '
%2$s
';
$items_markup = wp_dropdown_categories( $args );
$type = 'dropdown';
diff --git a/packages/block-library/src/categories/style.scss b/packages/block-library/src/categories/style.scss
index b87e28a80f0f3..319206c51a1f3 100644
--- a/packages/block-library/src/categories/style.scss
+++ b/packages/block-library/src/categories/style.scss
@@ -14,4 +14,8 @@
&.wp-block-categories-dropdown.aligncenter {
text-align: center;
}
+ & .wp-block-categories__label {
+ width: 100%;
+ display: block;
+ }
}
diff --git a/packages/block-library/src/footnotes/index.js b/packages/block-library/src/footnotes/index.js
index c5e851af7e033..ba5da984fd71e 100644
--- a/packages/block-library/src/footnotes/index.js
+++ b/packages/block-library/src/footnotes/index.js
@@ -21,8 +21,7 @@ export const settings = {
edit,
};
-registerFormatType( formatName, format );
-
export const init = () => {
+ registerFormatType( formatName, format );
initBlock( { name, metadata, settings } );
};
diff --git a/packages/block-library/src/gallery/block.json b/packages/block-library/src/gallery/block.json
index e9018704bf6bf..6a2129ec1e056 100644
--- a/packages/block-library/src/gallery/block.json
+++ b/packages/block-library/src/gallery/block.json
@@ -112,6 +112,16 @@
"supports": {
"anchor": true,
"align": true,
+ "__experimentalBorder": {
+ "radius": true,
+ "color": true,
+ "width": true,
+ "style": true,
+ "__experimentalDefaultControls": {
+ "color": true,
+ "radius": true
+ }
+ },
"html": false,
"units": [ "px", "em", "rem", "vh", "vw" ],
"spacing": {
diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json
index 39e985038c323..0bbe19b01098e 100644
--- a/packages/block-library/src/heading/block.json
+++ b/packages/block-library/src/heading/block.json
@@ -30,6 +30,18 @@
"anchor": true,
"className": true,
"splitting": true,
+ "__experimentalBorder": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true,
+ "__experimentalDefaultControls": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true
+ }
+ },
"color": {
"gradients": true,
"link": true,
diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php
index 584318b51d196..886843d83ba72 100644
--- a/packages/block-library/src/image/index.php
+++ b/packages/block-library/src/image/index.php
@@ -185,22 +185,37 @@ function block_core_image_render_lightbox( $block_content, $block ) {
$p->seek( 'figure' );
$figure_class_names = $p->get_attribute( 'class' );
$figure_styles = $p->get_attribute( 'style' );
+
+ // Create unique id and set the image metadata in the state.
+ $unique_image_id = uniqid();
+
+ wp_interactivity_state(
+ 'core/image',
+ array(
+ 'metadata' => array(
+ $unique_image_id => array(
+ 'uploadedSrc' => $img_uploaded_src,
+ 'figureClassNames' => $figure_class_names,
+ 'figureStyles' => $figure_styles,
+ 'imgClassNames' => $img_class_names,
+ 'imgStyles' => $img_styles,
+ 'targetWidth' => $img_width,
+ 'targetHeight' => $img_height,
+ 'scaleAttr' => $block['attrs']['scale'] ?? false,
+ 'ariaLabel' => $aria_label,
+ 'alt' => $alt,
+ ),
+ ),
+ )
+ );
+
$p->add_class( 'wp-lightbox-container' );
$p->set_attribute( 'data-wp-interactive', 'core/image' );
$p->set_attribute(
'data-wp-context',
wp_json_encode(
array(
- 'uploadedSrc' => $img_uploaded_src,
- 'figureClassNames' => $figure_class_names,
- 'figureStyles' => $figure_styles,
- 'imgClassNames' => $img_class_names,
- 'imgStyles' => $img_styles,
- 'targetWidth' => $img_width,
- 'targetHeight' => $img_height,
- 'scaleAttr' => $block['attrs']['scale'] ?? false,
- 'ariaLabel' => $aria_label,
- 'alt' => $alt,
+ 'imageId' => $unique_image_id,
),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
)
@@ -231,8 +246,8 @@ class="lightbox-trigger"
aria-label="' . esc_attr( $aria_label ) . '"
data-wp-init="callbacks.initTriggerButton"
data-wp-on-async--click="actions.showLightbox"
- data-wp-style--right="context.imageButtonRight"
- data-wp-style--top="context.imageButtonTop"
+ data-wp-style--right="state.imageButtonRight"
+ data-wp-style--top="state.imageButtonTop"
>
);
diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js
index 77f9fb211e335..1d5f516ee356d 100644
--- a/packages/block-library/src/query/edit/query-content.js
+++ b/packages/block-library/src/query/edit/query-content.js
@@ -49,7 +49,10 @@ export default function QueryContent( {
const { postsPerPage } = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
const { getEntityRecord, canUser } = select( coreStore );
- const settingPerPage = canUser( 'read', 'settings' )
+ const settingPerPage = canUser( 'read', {
+ kind: 'root',
+ name: 'site',
+ } )
? +getEntityRecord( 'root', 'site' )?.posts_per_page
: +getSettings().postsPerPage;
return {
diff --git a/packages/block-library/src/query/edit/query-placeholder.js b/packages/block-library/src/query/edit/query-placeholder.js
index 1b81a2893ec4f..eec982c9750cf 100644
--- a/packages/block-library/src/query/edit/query-placeholder.js
+++ b/packages/block-library/src/query/edit/query-placeholder.js
@@ -25,7 +25,6 @@ export default function QueryPlaceholder( {
clientId,
name,
openPatternSelectionModal,
- setAttributes,
} ) {
const [ isStartingBlank, setIsStartingBlank ] = useState( false );
const blockProps = useBlockProps();
@@ -64,7 +63,6 @@ export default function QueryPlaceholder( {
@@ -101,13 +99,7 @@ export default function QueryPlaceholder( {
);
}
-function QueryVariationPicker( {
- clientId,
- attributes,
- setAttributes,
- icon,
- label,
-} ) {
+function QueryVariationPicker( { clientId, attributes, icon, label } ) {
const scopeVariations = useScopedBlockVariations( attributes );
const { replaceInnerBlocks } = useDispatch( blockEditorStore );
const blockProps = useBlockProps();
@@ -118,18 +110,6 @@ function QueryVariationPicker( {
label={ label }
variations={ scopeVariations }
onSelect={ ( variation ) => {
- if ( variation.attributes ) {
- setAttributes( {
- ...variation.attributes,
- query: {
- ...variation.attributes.query,
- postType:
- attributes.query.postType ||
- variation.attributes.query.postType,
- },
- namespace: attributes.namespace,
- } );
- }
if ( variation.innerBlocks ) {
replaceInnerBlocks(
clientId,
diff --git a/packages/block-library/src/query/editor.scss b/packages/block-library/src/query/editor.scss
index 67d98f88359ef..b86eae0cbf65a 100644
--- a/packages/block-library/src/query/editor.scss
+++ b/packages/block-library/src/query/editor.scss
@@ -49,15 +49,6 @@
}
}
-.block-library-query-toolspanel__filters {
- .components-form-token-field__help {
- margin-bottom: 0;
- }
- .block-library-query-inspector__taxonomy-control:not(:last-child) {
- margin-bottom: $grid-unit-30;
- }
-}
-
.wp-block-query__enhanced-pagination-modal {
@include break-small() {
max-width: $break-mobile;
diff --git a/packages/block-library/src/quote/block.json b/packages/block-library/src/quote/block.json
index 7ca81b364b3ef..c695c938923ae 100644
--- a/packages/block-library/src/quote/block.json
+++ b/packages/block-library/src/quote/block.json
@@ -36,6 +36,18 @@
"backgroundImage": true
}
},
+ "__experimentalBorder": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true,
+ "__experimentalDefaultControls": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true
+ }
+ },
"dimensions": {
"minHeight": true,
"__experimentalDefaultControls": {
@@ -70,7 +82,9 @@
"allowEditing": false
},
"spacing": {
- "blockGap": true
+ "blockGap": true,
+ "padding": true,
+ "margin": true
},
"interactivity": {
"clientNavigation": true
diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js
index 046b5feeb20c9..f3e2f9e13a11a 100644
--- a/packages/block-library/src/site-logo/edit.js
+++ b/packages/block-library/src/site-logo/edit.js
@@ -403,7 +403,10 @@ export default function LogoEdit( {
} = useSelect( ( select ) => {
const { canUser, getEntityRecord, getEditedEntityRecord } =
select( coreStore );
- const _canUserEdit = canUser( 'update', 'settings' );
+ const _canUserEdit = canUser( 'update', {
+ kind: 'root',
+ name: 'site',
+ } );
const siteSettings = _canUserEdit
? getEditedEntityRecord( 'root', 'site' )
: undefined;
diff --git a/packages/block-library/src/site-tagline/edit.js b/packages/block-library/src/site-tagline/edit.js
index fea91f867053a..15f8a789dfbe9 100644
--- a/packages/block-library/src/site-tagline/edit.js
+++ b/packages/block-library/src/site-tagline/edit.js
@@ -29,12 +29,15 @@ export default function SiteTaglineEdit( {
const { canUserEdit, tagline } = useSelect( ( select ) => {
const { canUser, getEntityRecord, getEditedEntityRecord } =
select( coreStore );
- const canEdit = canUser( 'update', 'settings' );
+ const canEdit = canUser( 'update', {
+ kind: 'root',
+ name: 'site',
+ } );
const settings = canEdit ? getEditedEntityRecord( 'root', 'site' ) : {};
const readOnlySettings = getEntityRecord( 'root', '__unstableBase' );
return {
- canUserEdit: canUser( 'update', 'settings' ),
+ canUserEdit: canEdit,
tagline: canEdit
? settings?.description
: readOnlySettings?.description,
diff --git a/packages/block-library/src/site-title/edit.js b/packages/block-library/src/site-title/edit.js
index 850268bf4ace5..76519ac1297b1 100644
--- a/packages/block-library/src/site-title/edit.js
+++ b/packages/block-library/src/site-title/edit.js
@@ -32,7 +32,10 @@ export default function SiteTitleEdit( {
const { canUserEdit, title } = useSelect( ( select ) => {
const { canUser, getEntityRecord, getEditedEntityRecord } =
select( coreStore );
- const canEdit = canUser( 'update', 'settings' );
+ const canEdit = canUser( 'update', {
+ kind: 'root',
+ name: 'site',
+ } );
const settings = canEdit ? getEditedEntityRecord( 'root', 'site' ) : {};
const readOnlySettings = getEntityRecord( 'root', '__unstableBase' );
diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js
index 52d740735eaf6..0cb6cfc8996ce 100644
--- a/packages/block-library/src/template-part/edit/index.js
+++ b/packages/block-library/src/template-part/edit/index.js
@@ -151,8 +151,10 @@ export default function TemplatePartEdit( {
)
: false;
- const _canEditTemplate =
- select( coreStore ).canUser( 'create', 'templates' ) ?? false;
+ const _canEditTemplate = select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template_part',
+ } );
return {
hasInnerBlocks: getBlockCount( clientId ) > 0,
@@ -165,7 +167,7 @@ export default function TemplatePartEdit( {
onNavigateToEntityRecord:
getSettings().onNavigateToEntityRecord,
title: entityRecord?.title,
- canEditTemplate: _canEditTemplate,
+ canEditTemplate: !! _canEditTemplate,
};
},
[ templatePartId, attributes.area, clientId ]
diff --git a/packages/block-library/src/template-part/edit/inner-blocks.js b/packages/block-library/src/template-part/edit/inner-blocks.js
index b0d8794fb8f23..ef7d85948626d 100644
--- a/packages/block-library/src/template-part/edit/inner-blocks.js
+++ b/packages/block-library/src/template-part/edit/inner-blocks.js
@@ -127,11 +127,14 @@ export default function TemplatePartInnerBlocks( {
const { canViewTemplatePart, canEditTemplatePart } = useSelect(
( select ) => {
return {
- canViewTemplatePart:
- select( coreStore ).canUser( 'read', 'templates' ) ?? false,
- canEditTemplatePart:
- select( coreStore ).canUser( 'create', 'templates' ) ??
- false,
+ canViewTemplatePart: !! select( coreStore ).canUser( 'read', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ),
+ canEditTemplatePart: !! select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ),
};
},
[]
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 21ece7c6aaed3..aa72d126dfb69 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,8 +2,13 @@
## Unreleased
+### Deprecations
+
+- `FormTokenField`: Deprecate bottom margin. Add a `__nextHasNoMarginBottom` prop to start opting into the margin-free styles that will become the default in a future version, currently scheduled to be WordPress 7.0 ([#63491](https://github.com/WordPress/gutenberg/pull/63491)).
+
### Bug Fixes
+- `ComboboxControl`: Fix ComboboxControl reset button when using the keyboard. ([#63410](https://github.com/WordPress/gutenberg/pull/63410))
- `Button`: Never apply `aria-disabled` to anchor ([#63376](https://github.com/WordPress/gutenberg/pull/63376)).
### Internal
@@ -12,6 +17,7 @@
- `CustomSelectControlV2`: animate select popover appearance. ([#63343](https://github.com/WordPress/gutenberg/pull/63343))
- `CustomSelectControlV2`: do not flip popover if legacy adapter. ([#63357](https://github.com/WordPress/gutenberg/pull/63357)).
- `DropdownMenuV2`: invert animation direction. ([#63443](https://github.com/WordPress/gutenberg/pull/63443)).
+- `Tabs`: Vertical Tabs should be 40px min height. ([#63446](https://github.com/WordPress/gutenberg/pull/63446)).
### Enhancements
diff --git a/packages/components/src/combobox-control/index.tsx b/packages/components/src/combobox-control/index.tsx
index d22bbba2a9d24..a39a5dc1fc541 100644
--- a/packages/components/src/combobox-control/index.tsx
+++ b/packages/components/src/combobox-control/index.tsx
@@ -267,6 +267,15 @@ function ComboboxControl( props: ComboboxControlProps ) {
inputContainer.current?.focus();
};
+ // Stop propagation of the keydown event when pressing Enter on the Reset
+ // button to prevent calling the onKeydown callback on the container div
+ // element which actually sets the selected suggestion.
+ const handleResetStopPropagation: React.KeyboardEventHandler<
+ HTMLButtonElement
+ > = ( event ) => {
+ event.stopPropagation();
+ };
+
// Update current selections when the filter input changes.
useEffect( () => {
const hasMatchingSuggestions = matchingSuggestions.length > 0;
@@ -350,6 +359,7 @@ function ComboboxControl( props: ComboboxControlProps ) {
// eslint-disable-next-line no-restricted-syntax
disabled={ ! value }
onClick={ handleOnReset }
+ onKeyDown={ handleResetStopPropagation }
label={ __( 'Reset' ) }
/>
diff --git a/packages/components/src/combobox-control/test/index.tsx b/packages/components/src/combobox-control/test/index.tsx
index 37802e4669c24..76ce9cc4724c5 100644
--- a/packages/components/src/combobox-control/test/index.tsx
+++ b/packages/components/src/combobox-control/test/index.tsx
@@ -89,7 +89,6 @@ describe.each( [
);
const label = getLabel( defaultLabelText );
- expect( label ).toBeInTheDocument();
expect( label ).toBeVisible();
} );
@@ -306,4 +305,131 @@ describe.each( [
expect( onChangeSpy ).toHaveBeenCalledWith( targetOption.value );
expect( input ).toHaveValue( targetOption.label );
} );
+
+ it( 'should render with Reset button disabled', () => {
+ render(
+
+ );
+
+ const resetButton = screen.getByRole( 'button', { name: 'Reset' } );
+
+ expect( resetButton ).toBeVisible();
+ expect( resetButton ).toBeDisabled();
+ } );
+
+ it( 'should reset input when clicking the Reset button', async () => {
+ const user = await userEvent.setup();
+ const targetOption = timezones[ 13 ];
+
+ render(
+
+ );
+
+ // Pressing tab selects the input and shows the options.
+ await user.tab();
+ // Type enough characters to ensure a predictable search result.
+ await user.keyboard( getOptionSearchString( targetOption ) );
+ // Pressing Enter/Return selects the currently focused option.
+ await user.keyboard( '{Enter}' );
+
+ const input = getInput( defaultLabelText );
+
+ expect( input ).toHaveValue( targetOption.label );
+
+ const resetButton = screen.getByRole( 'button', { name: 'Reset' } );
+
+ expect( resetButton ).toBeEnabled();
+
+ await user.click( resetButton );
+
+ expect( input ).toHaveValue( '' );
+ expect( resetButton ).toBeDisabled();
+ expect( input ).toHaveFocus();
+ } );
+
+ it( 'should reset input when pressing the Reset button with the Enter key', async () => {
+ const user = await userEvent.setup();
+ const targetOption = timezones[ 13 ];
+
+ render(
+
+ );
+
+ // Pressing tab selects the input and shows the options.
+ await user.tab();
+ // Type enough characters to ensure a predictable search result.
+ await user.keyboard( getOptionSearchString( targetOption ) );
+ // Pressing Enter/Return selects the currently focused option.
+ await user.keyboard( '{Enter}' );
+
+ const input = getInput( defaultLabelText );
+
+ expect( input ).toHaveValue( targetOption.label );
+
+ // Pressing tab moves focus to the Reset buttons
+ await user.tab();
+
+ const resetButton = screen.getByRole( 'button', { name: 'Reset' } );
+
+ // If the button has focus that implies it is enabled.
+ expect( resetButton ).toHaveFocus();
+
+ // Pressing Enter/Return resets the input.
+ await user.keyboard( '{Enter}' );
+
+ expect( input ).toHaveValue( '' );
+ expect( resetButton ).toBeDisabled();
+ expect( input ).toHaveFocus();
+ } );
+
+ it( 'should reset input when pressing the Reset button with the Spacebar key', async () => {
+ const user = await userEvent.setup();
+ const targetOption = timezones[ 13 ];
+
+ render(
+
+ );
+
+ // Pressing tab selects the input and shows the options.
+ await user.tab();
+ // Type enough characters to ensure a predictable search result.
+ await user.keyboard( getOptionSearchString( targetOption ) );
+ // Pressing Enter/Return selects the currently focused option.
+ await user.keyboard( '{Enter}' );
+
+ const input = getInput( defaultLabelText );
+
+ expect( input ).toHaveValue( targetOption.label );
+
+ // Pressing tab moves focus to the Reset buttons.
+ await user.tab();
+
+ const resetButton = screen.getByRole( 'button', { name: 'Reset' } );
+
+ // If the button has focus that implies it is enabled.
+ expect( resetButton ).toHaveFocus();
+
+ // Pressing Spacebar resets the input.
+ await user.keyboard( '[Space]' );
+
+ expect( input ).toHaveValue( '' );
+ expect( resetButton ).toBeDisabled();
+ expect( input ).toHaveFocus();
+ } );
} );
diff --git a/packages/components/src/font-size-picker/README.md b/packages/components/src/font-size-picker/README.md
index 2a342d3de9114..790468acc6c1f 100644
--- a/packages/components/src/font-size-picker/README.md
+++ b/packages/components/src/font-size-picker/README.md
@@ -92,7 +92,7 @@ Size of the control.
Available units for custom font size selection.
- Required: No
-- Default: `[ 'px', 'em', 'rem' ]`
+- Default: `[ 'px', 'em', 'rem', 'vw', 'vh' ]`
### `value`: `number | string`
diff --git a/packages/components/src/font-size-picker/types.ts b/packages/components/src/font-size-picker/types.ts
index 0072c47df6f05..39016849fd9f9 100644
--- a/packages/components/src/font-size-picker/types.ts
+++ b/packages/components/src/font-size-picker/types.ts
@@ -29,7 +29,7 @@ export type FontSizePickerProps = {
/**
* Available units for custom font size selection.
*
- * @default `[ 'px', 'em', 'rem' ]`
+ * @default [ 'px', 'em', 'rem', 'vw', 'vh' ]
*/
units?: string[];
/**
diff --git a/packages/components/src/form-token-field/README.md b/packages/components/src/form-token-field/README.md
index f78aec8301495..3da19ba534d31 100644
--- a/packages/components/src/form-token-field/README.md
+++ b/packages/components/src/form-token-field/README.md
@@ -61,7 +61,7 @@ The `value` property is handled in a manner similar to controlled form component
- `__experimentalShowHowTo` - If false, the text on how to use the select (ie: _Separate with commas or the Enter key._) will be hidden.
- `__experimentalValidateInput` - If passed, all introduced values will be validated before being added as tokens.
- `__experimentalAutoSelectFirstMatch` - If true, the select the first matching suggestion when the user presses the Enter key (or space when tokenizeOnSpace is true).
-- `__nextHasNoMarginBottom` - Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 6.5. (The prop can be safely removed once this happens.)
+- `__nextHasNoMarginBottom` - Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 7.0. (The prop can be safely removed once this happens.)
- `tokenizeOnBlur` - If true, add any incompleteTokenValue as a new token when the field loses focus.
## Usage
@@ -87,6 +87,7 @@ const MyFormTokenField = () => {
value={ selectedContinents }
suggestions={ continents }
onChange={ ( tokens ) => setSelectedContinents( tokens ) }
+ __nextHasNoMarginBottom
/>
);
};
diff --git a/packages/components/src/form-token-field/index.tsx b/packages/components/src/form-token-field/index.tsx
index 9fa3d3b7323df..9f05dfee008da 100644
--- a/packages/components/src/form-token-field/index.tsx
+++ b/packages/components/src/form-token-field/index.tsx
@@ -12,6 +12,7 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import { useDebounce, useInstanceId, usePrevious } from '@wordpress/compose';
import { speak } from '@wordpress/a11y';
import isShallowEqual from '@wordpress/is-shallow-equal';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
@@ -77,6 +78,14 @@ export function FormTokenField( props: FormTokenFieldProps ) {
tokenizeOnBlur = false,
} = useDeprecated36pxDefaultSizeProp< FormTokenFieldProps >( props );
+ if ( ! __nextHasNoMarginBottom ) {
+ deprecated( 'Bottom margin styles for wp.components.FormTokenField', {
+ 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.',
+ } );
+ }
+
const instanceId = useInstanceId( FormTokenField );
// We reset to these initial values again in the onBlur
diff --git a/packages/components/src/form-token-field/stories/index.story.tsx b/packages/components/src/form-token-field/stories/index.story.tsx
index da61a0b313bfe..117dec9e9df5a 100644
--- a/packages/components/src/form-token-field/stories/index.story.tsx
+++ b/packages/components/src/form-token-field/stories/index.story.tsx
@@ -62,6 +62,7 @@ export const Default: StoryFn< typeof FormTokenField > = DefaultTemplate.bind(
Default.args = {
label: 'Type a continent',
suggestions: continents,
+ __nextHasNoMarginBottom: true,
};
export const Async: StoryFn< typeof FormTokenField > = ( {
@@ -99,6 +100,7 @@ export const Async: StoryFn< typeof FormTokenField > = ( {
Async.args = {
label: 'Type a continent',
suggestions: continents,
+ __nextHasNoMarginBottom: true,
};
export const DropdownSelector: StoryFn< typeof FormTokenField > =
diff --git a/packages/components/src/tabs/styles.ts b/packages/components/src/tabs/styles.ts
index 88e54285d619b..7d67f462c7e87 100644
--- a/packages/components/src/tabs/styles.ts
+++ b/packages/components/src/tabs/styles.ts
@@ -120,6 +120,12 @@ export const Tab = styled( Ariakit.Tab )`
opacity: 1;
}
}
+
+ [aria-orientation='vertical'] & {
+ min-height: ${ space(
+ 10
+ ) }; // Avoid fixed height to allow for long strings that go in multiple lines.
+ }
`;
export const TabPanel = styled( Ariakit.TabPanel )`
diff --git a/packages/core-commands/src/site-editor-navigation-commands.js b/packages/core-commands/src/site-editor-navigation-commands.js
index 46b7751ee525d..cb38ea2bb2553 100644
--- a/packages/core-commands/src/site-editor-navigation-commands.js
+++ b/packages/core-commands/src/site-editor-navigation-commands.js
@@ -266,7 +266,10 @@ function useSiteEditorBasicNavigationCommands() {
'site-editor.php'
);
const canCreateTemplate = useSelect( ( select ) => {
- return select( coreStore ).canUser( 'create', 'templates' );
+ return select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } );
}, [] );
const isBlockBasedTheme = useIsBlockBasedTheme();
const commands = useMemo( () => {
diff --git a/packages/core-data/src/test/entity-provider.js b/packages/core-data/src/test/entity-provider.js
index aa373e6304aad..72bb083153e06 100644
--- a/packages/core-data/src/test/entity-provider.js
+++ b/packages/core-data/src/test/entity-provider.js
@@ -14,7 +14,8 @@ import {
} from '@wordpress/blocks';
import { RichText, useBlockProps } from '@wordpress/block-editor';
import { createRegistry, RegistryProvider } from '@wordpress/data';
-import '@wordpress/block-library';
+import { registerCoreBlocks } from '@wordpress/block-library';
+import { unregisterFormatType } from '@wordpress/rich-text';
/**
* Internal dependencies
@@ -137,12 +138,15 @@ describe( 'useEntityBlockEditor', () => {
title: 'block title',
edit,
} );
+
+ registerCoreBlocks();
} );
afterEach( () => {
getBlockTypes().forEach( ( block ) => {
unregisterBlockType( block.name );
} );
+ unregisterFormatType( 'core/footnote' );
} );
it( 'does not mutate block attributes that include an array of strings or null values', async () => {
diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js
index a42ec50097bcb..d3c78a3d6d012 100644
--- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js
+++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js
@@ -51,7 +51,10 @@ export default function SidebarBlockEditor( {
const { get } = select( preferencesStore );
return {
hasUploadPermissions:
- select( coreStore ).canUser( 'create', 'media' ) ?? true,
+ select( coreStore ).canUser( 'create', {
+ kind: 'root',
+ name: 'media',
+ } ) ?? true,
isFixedToolbarActive: !! get(
'core/customize-widgets',
'fixedToolbar'
diff --git a/packages/customize-widgets/src/filters/move-to-sidebar.js b/packages/customize-widgets/src/filters/move-to-sidebar.js
index 7677a5e1726cf..4562afb3817c6 100644
--- a/packages/customize-widgets/src/filters/move-to-sidebar.js
+++ b/packages/customize-widgets/src/filters/move-to-sidebar.js
@@ -83,7 +83,7 @@ const withMoveToSidebarToolbarItem = createHigherOrderComponent(
return (
<>
-
+
{ hasMultipleSidebars && canInsertBlockInSidebar && (
widget.id_base === idBase
)?.is_wide ?? false;
- return ;
+ return ;
},
'withWideWidgetDisplay'
);
diff --git a/packages/dataviews/src/bulk-actions.tsx b/packages/dataviews/src/bulk-actions.tsx
index 3530b0b849c90..2e3cc495e48b7 100644
--- a/packages/dataviews/src/bulk-actions.tsx
+++ b/packages/dataviews/src/bulk-actions.tsx
@@ -160,7 +160,9 @@ function ActionsMenuGroup< Item >( {
);
} );
}, [ actions, selectedItems ] );
-
+ if ( ! elligibleActions.length ) {
+ return null;
+ }
return (
<>
diff --git a/packages/dataviews/src/layouts/table/column-header-menu.tsx b/packages/dataviews/src/layouts/table/column-header-menu.tsx
new file mode 100644
index 0000000000000..7224701798bba
--- /dev/null
+++ b/packages/dataviews/src/layouts/table/column-header-menu.tsx
@@ -0,0 +1,268 @@
+/**
+ * External dependencies
+ */
+import type { ReactNode, Ref, PropsWithoutRef, RefAttributes } from 'react';
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { arrowLeft, arrowRight, unseen, funnel } from '@wordpress/icons';
+import {
+ Button,
+ Icon,
+ privateApis as componentsPrivateApis,
+} from '@wordpress/components';
+import { forwardRef, Children, Fragment } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import { sanitizeOperators } from '../../utils';
+import { SORTING_DIRECTIONS, sortArrows, sortLabels } from '../../constants';
+import type {
+ NormalizedField,
+ SortDirection,
+ ViewTable as ViewTableType,
+} from '../../types';
+
+const {
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuGroupV2: DropdownMenuGroup,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuRadioItemV2: DropdownMenuRadioItem,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
+ DropdownMenuSeparatorV2: DropdownMenuSeparator,
+} = unlock( componentsPrivateApis );
+
+interface HeaderMenuProps< Item > {
+ fieldId: string;
+ view: ViewTableType;
+ fields: NormalizedField< Item >[];
+ onChangeView: ( view: ViewTableType ) => void;
+ onHide: ( field: NormalizedField< Item > ) => void;
+ setOpenedFilter: ( fieldId: string ) => void;
+}
+
+function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) {
+ return Children.toArray( children )
+ .filter( Boolean )
+ .map( ( child, i ) => (
+
+ { i > 0 && }
+ { child }
+
+ ) );
+}
+
+const _HeaderMenu = forwardRef( function HeaderMenu< Item >(
+ {
+ fieldId,
+ view,
+ fields,
+ onChangeView,
+ onHide,
+ setOpenedFilter,
+ }: HeaderMenuProps< Item >,
+ ref: Ref< HTMLButtonElement >
+) {
+ const combinedField = view.layout?.combinedFields?.find(
+ ( f ) => f.id === fieldId
+ );
+ const index = view.fields?.indexOf( fieldId ) as number;
+ if ( !! combinedField ) {
+ return combinedField.header;
+ }
+ const field = fields.find( ( f ) => f.id === fieldId );
+ if ( ! field ) {
+ return null;
+ }
+ const isHidable = field.enableHiding !== false;
+ const isSortable = field.enableSorting !== false;
+ const isSorted = view.sort?.field === field.id;
+ const operators = sanitizeOperators( field );
+ // Filter can be added:
+ // 1. If the field is not already part of a view's filters.
+ // 2. If the field meets the type and operator requirements.
+ // 3. If it's not primary. If it is, it should be already visible.
+ const canAddFilter =
+ ! view.filters?.some( ( _filter ) => field.id === _filter.field ) &&
+ !! field.elements?.length &&
+ !! operators.length &&
+ ! field.filterBy?.isPrimary;
+ if ( ! isSortable && ! isHidable && ! canAddFilter ) {
+ return field.header;
+ }
+ return (
+
+ { field.header }
+ { view.sort && isSorted && (
+
+ { sortArrows[ view.sort.direction ] }
+
+ ) }
+
+ }
+ style={ { minWidth: '240px' } }
+ >
+
+ { isSortable && (
+
+ { SORTING_DIRECTIONS.map(
+ ( direction: SortDirection ) => {
+ const isChecked =
+ view.sort &&
+ isSorted &&
+ view.sort.direction === direction;
+
+ const value = `${ field.id }-${ direction }`;
+
+ return (
+ {
+ onChangeView( {
+ ...view,
+ sort: {
+ field: field.id,
+ direction,
+ },
+ } );
+ } }
+ >
+
+ { sortLabels[ direction ] }
+
+
+ );
+ }
+ ) }
+
+ ) }
+ { canAddFilter && (
+
+ }
+ onClick={ () => {
+ setOpenedFilter( field.id );
+ onChangeView( {
+ ...view,
+ page: 1,
+ filters: [
+ ...( view.filters || [] ),
+ {
+ field: field.id,
+ value: undefined,
+ operator: operators[ 0 ],
+ },
+ ],
+ } );
+ } }
+ >
+
+ { __( 'Add filter' ) }
+
+
+
+ ) }
+
+ }
+ disabled={ index < 1 }
+ onClick={ () => {
+ if ( ! view.fields || index < 1 ) {
+ return;
+ }
+ onChangeView( {
+ ...view,
+ fields: [
+ ...( view.fields.slice( 0, index - 1 ) ??
+ [] ),
+ field.id,
+ view.fields[ index - 1 ],
+ ...view.fields.slice( index + 1 ),
+ ],
+ } );
+ } }
+ >
+
+ { __( 'Move left' ) }
+
+
+ }
+ disabled={
+ ! view.fields || index >= view.fields.length - 1
+ }
+ onClick={ () => {
+ if (
+ ! view.fields ||
+ index >= view.fields.length - 1
+ ) {
+ return;
+ }
+ onChangeView( {
+ ...view,
+ fields: [
+ ...( view.fields.slice( 0, index ) ?? [] ),
+ view.fields[ index + 1 ],
+ field.id,
+ ...view.fields.slice( index + 2 ),
+ ],
+ } );
+ } }
+ >
+
+ { __( 'Move right' ) }
+
+
+ { isHidable && (
+ }
+ onClick={ () => {
+ const viewFields =
+ view.fields || fields.map( ( f ) => f.id );
+ onHide( field );
+ onChangeView( {
+ ...view,
+ fields: viewFields.filter(
+ ( id ) => id !== field.id
+ ),
+ } );
+ } }
+ >
+
+ { __( 'Hide' ) }
+
+
+ ) }
+
+
+
+ );
+} );
+
+// @ts-expect-error Lift the `Item` type argument through the forwardRef.
+const ColumnHeaderMenu: < Item >(
+ props: PropsWithoutRef< HeaderMenuProps< Item > > &
+ RefAttributes< HTMLButtonElement >
+) => ReturnType< typeof _HeaderMenu > = _HeaderMenu;
+
+export default ColumnHeaderMenu;
diff --git a/packages/dataviews/src/layouts/table/index.tsx b/packages/dataviews/src/layouts/table/index.tsx
index da6658d3f903e..cec094f363293 100644
--- a/packages/dataviews/src/layouts/table/index.tsx
+++ b/packages/dataviews/src/layouts/table/index.tsx
@@ -2,46 +2,31 @@
* External dependencies
*/
import clsx from 'clsx';
-import type { ReactNode, Ref, PropsWithoutRef, RefAttributes } from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import { unseen, funnel } from '@wordpress/icons';
import {
- Button,
- Icon,
- privateApis as componentsPrivateApis,
CheckboxControl,
Spinner,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
import {
- forwardRef,
useEffect,
useId,
useRef,
useState,
useMemo,
- Children,
- Fragment,
} from '@wordpress/element';
/**
* Internal dependencies
*/
import SingleSelectionCheckbox from '../../single-selection-checkbox';
-import { unlock } from '../../lock-unlock';
import ItemActions from '../../item-actions';
-import { sanitizeOperators } from '../../utils';
-import {
- SORTING_DIRECTIONS,
- sortArrows,
- sortLabels,
- sortValues,
-} from '../../constants';
+import { sortValues } from '../../constants';
import {
useSomeItemHasAPossibleBulkAction,
useHasAPossibleBulkAction,
@@ -49,30 +34,12 @@ import {
import type {
Action,
NormalizedField,
- SortDirection,
ViewTable as ViewTableType,
ViewTableProps,
CombinedField,
} from '../../types';
import type { SetSelection } from '../../private-types';
-
-const {
- DropdownMenuV2: DropdownMenu,
- DropdownMenuGroupV2: DropdownMenuGroup,
- DropdownMenuItemV2: DropdownMenuItem,
- DropdownMenuRadioItemV2: DropdownMenuRadioItem,
- DropdownMenuItemLabelV2: DropdownMenuItemLabel,
- DropdownMenuSeparatorV2: DropdownMenuSeparator,
-} = unlock( componentsPrivateApis );
-
-interface HeaderMenuProps< Item > {
- fieldId: string;
- view: ViewTableType;
- fields: NormalizedField< Item >[];
- onChangeView: ( view: ViewTableType ) => void;
- onHide: ( field: NormalizedField< Item > ) => void;
- setOpenedFilter: ( fieldId: string ) => void;
-}
+import ColumnHeaderMenu from './column-header-menu';
interface BulkSelectionCheckboxProps< Item > {
selection: string[];
@@ -117,173 +84,6 @@ interface TableRowProps< Item > {
onSelectionChange: SetSelection;
}
-function WithDropDownMenuSeparators( { children }: { children: ReactNode } ) {
- return Children.toArray( children )
- .filter( Boolean )
- .map( ( child, i ) => (
-
- { i > 0 && }
- { child }
-
- ) );
-}
-
-const _HeaderMenu = forwardRef( function HeaderMenu< Item >(
- {
- fieldId,
- view,
- fields,
- onChangeView,
- onHide,
- setOpenedFilter,
- }: HeaderMenuProps< Item >,
- ref: Ref< HTMLButtonElement >
-) {
- const combinedField = view.layout?.combinedFields?.find(
- ( f ) => f.id === fieldId
- );
- if ( !! combinedField ) {
- return combinedField.header;
- }
- const field = fields.find( ( f ) => f.id === fieldId );
- if ( ! field ) {
- return null;
- }
- const isHidable = field.enableHiding !== false;
- const isSortable = field.enableSorting !== false;
- const isSorted = view.sort?.field === field.id;
- const operators = sanitizeOperators( field );
- // Filter can be added:
- // 1. If the field is not already part of a view's filters.
- // 2. If the field meets the type and operator requirements.
- // 3. If it's not primary. If it is, it should be already visible.
- const canAddFilter =
- ! view.filters?.some( ( _filter ) => field.id === _filter.field ) &&
- !! field.elements?.length &&
- !! operators.length &&
- ! field.filterBy?.isPrimary;
- if ( ! isSortable && ! isHidable && ! canAddFilter ) {
- return field.header;
- }
- return (
-
- { field.header }
- { view.sort && isSorted && (
-
- { sortArrows[ view.sort.direction ] }
-
- ) }
-
- }
- style={ { minWidth: '240px' } }
- >
-
- { isSortable && (
-
- { SORTING_DIRECTIONS.map(
- ( direction: SortDirection ) => {
- const isChecked =
- view.sort &&
- isSorted &&
- view.sort.direction === direction;
-
- const value = `${ field.id }-${ direction }`;
-
- return (
- {
- onChangeView( {
- ...view,
- sort: {
- field: field.id,
- direction,
- },
- } );
- } }
- >
-
- { sortLabels[ direction ] }
-
-
- );
- }
- ) }
-
- ) }
- { canAddFilter && (
-
- }
- onClick={ () => {
- setOpenedFilter( field.id );
- onChangeView( {
- ...view,
- page: 1,
- filters: [
- ...( view.filters || [] ),
- {
- field: field.id,
- value: undefined,
- operator: operators[ 0 ],
- },
- ],
- } );
- } }
- >
-
- { __( 'Add filter' ) }
-
-
-
- ) }
- { isHidable && (
- }
- onClick={ () => {
- const viewFields =
- view.fields || fields.map( ( f ) => f.id );
- onHide( field );
- onChangeView( {
- ...view,
- fields: viewFields.filter(
- ( id ) => id !== field.id
- ),
- } );
- } }
- >
-
- { __( 'Hide' ) }
-
-
- ) }
-
-
- );
-} );
-
-// @ts-expect-error Lift the `Item` type argument through the forwardRef.
-const HeaderMenu: < Item >(
- props: PropsWithoutRef< HeaderMenuProps< Item > > &
- RefAttributes< HTMLButtonElement >
-) => ReturnType< typeof _HeaderMenu > = _HeaderMenu;
-
function BulkSelectionCheckbox< Item >( {
selection,
onSelectionChange,
@@ -594,7 +394,7 @@ function ViewTable< Item >( {
}
scope="col"
>
- {
if ( node ) {
headerMenuRefs.current.set(
diff --git a/packages/dataviews/src/pagination.tsx b/packages/dataviews/src/pagination.tsx
index 497ae80505bd7..99688e076e53b 100644
--- a/packages/dataviews/src/pagination.tsx
+++ b/packages/dataviews/src/pagination.tsx
@@ -8,7 +8,7 @@ import {
} from '@wordpress/components';
import { createInterpolateElement, memo } from '@wordpress/element';
import { sprintf, __, _x } from '@wordpress/i18n';
-import { chevronRight, chevronLeft } from '@wordpress/icons';
+import { next, previous } from '@wordpress/icons';
/**
* Internal dependencies
@@ -92,7 +92,7 @@ const Pagination = memo( function Pagination( {
disabled={ currentPage === 1 }
accessibleWhenDisabled
label={ __( 'Previous page' ) }
- icon={ chevronLeft }
+ icon={ previous }
showTooltip
size="compact"
tooltipPosition="top"
@@ -104,7 +104,7 @@ const Pagination = memo( function Pagination( {
disabled={ currentPage >= totalPages }
accessibleWhenDisabled
label={ __( 'Next page' ) }
- icon={ chevronRight }
+ icon={ next }
showTooltip
size="compact"
tooltipPosition="top"
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index 07c4e8f3b4595..b6ab4629b9e21 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -198,7 +198,10 @@ function Layout( {
const supportsTemplateMode = settings.supportsTemplateMode;
const isViewable =
getPostType( currentPost.postType )?.viewable ?? false;
- const canViewTemplate = canUser( 'read', 'templates' );
+ const canViewTemplate = canUser( 'read', {
+ kind: 'postType',
+ name: 'wp_template',
+ } );
return {
mode: select( editorStore ).getEditorMode(),
diff --git a/packages/edit-post/src/components/more-menu/manage-patterns-menu-item.js b/packages/edit-post/src/components/more-menu/manage-patterns-menu-item.js
index abb009cbc288e..786ba91f5e04a 100644
--- a/packages/edit-post/src/components/more-menu/manage-patterns-menu-item.js
+++ b/packages/edit-post/src/components/more-menu/manage-patterns-menu-item.js
@@ -20,7 +20,12 @@ function ManagePatternsMenuItem() {
// The site editor and templates both check whether the user has
// edit_theme_options capabilities. We can leverage that here and not
// display the manage patterns link if the user can't access it.
- return canUser( 'create', 'templates' ) ? patternsUrl : defaultUrl;
+ return canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } )
+ ? patternsUrl
+ : defaultUrl;
}, [] );
return (
diff --git a/packages/edit-site/src/components/add-new-pattern/index.js b/packages/edit-site/src/components/add-new-pattern/index.js
index 1bb9a13c646e2..e97cef1b2aa91 100644
--- a/packages/edit-site/src/components/add-new-pattern/index.js
+++ b/packages/edit-site/src/components/add-new-pattern/index.js
@@ -56,8 +56,14 @@ export default function AddNewPattern() {
addNewTemplatePartLabel: getPostType( TEMPLATE_PART_POST_TYPE )
?.labels?.add_new_item,
// Blocks refers to the wp_block post type, this checks the ability to create a post of that type.
- canCreatePattern: canUser( 'create', 'blocks' ),
- canCreateTemplatePart: canUser( 'create', 'template-parts' ),
+ canCreatePattern: canUser( 'create', {
+ kind: 'postType',
+ name: PATTERN_TYPES.user,
+ } ),
+ canCreateTemplatePart: canUser( 'create', {
+ kind: 'postType',
+ name: TEMPLATE_PART_POST_TYPE,
+ } ),
};
}, [] );
diff --git a/packages/edit-site/src/components/global-styles/block-preview-panel.js b/packages/edit-site/src/components/global-styles/block-preview-panel.js
index 82f429e0e2c04..6b1b980377c9c 100644
--- a/packages/edit-site/src/components/global-styles/block-preview-panel.js
+++ b/packages/edit-site/src/components/global-styles/block-preview-panel.js
@@ -35,6 +35,12 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => {
const viewportWidth = blockExample?.viewportWidth ?? 500;
// Same as height of InserterPreviewPanel.
const previewHeight = 144;
+ const sidebarWidth = 235;
+ const scale = sidebarWidth / viewportWidth;
+ const minHeight =
+ scale !== 0 && scale < 1 && previewHeight
+ ? previewHeight / scale
+ : previewHeight;
if ( ! blockExample ) {
return null;
@@ -57,7 +63,7 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => {
css: `
body{
padding: 24px;
- min-height:100%;
+ min-height:${ Math.round( minHeight ) }px;
display:flex;
align-items:center;
}
diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/index.js
index 9b90db91ea3f6..80edc1596f08c 100644
--- a/packages/edit-site/src/components/global-styles/font-library-modal/index.js
+++ b/packages/edit-site/src/components/global-styles/font-library-modal/index.js
@@ -46,8 +46,10 @@ function FontLibraryModal( {
} ) {
const { collections, setNotice } = useContext( FontLibraryContext );
const canUserCreate = useSelect( ( select ) => {
- const { canUser } = select( coreStore );
- return canUser( 'create', 'font-families' );
+ return select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_font_family',
+ } );
}, [] );
const tabs = [ DEFAULT_TAB ];
diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js
index 564f972ce10f0..c1e2f12edcb5e 100644
--- a/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js
+++ b/packages/edit-site/src/components/global-styles/font-library-modal/installed-fonts.js
@@ -93,7 +93,11 @@ function InstalledFonts() {
const { canUser } = select( coreStore );
return (
customFontFamilyId &&
- canUser( 'delete', 'font-families', customFontFamilyId )
+ canUser( 'delete', {
+ kind: 'postType',
+ name: 'wp_font_family',
+ id: customFontFamilyId,
+ } )
);
},
[ customFontFamilyId ]
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 fd71cdfa63068..a5358388e89c7 100644
--- a/packages/edit-site/src/components/global-styles/screen-block.js
+++ b/packages/edit-site/src/components/global-styles/screen-block.js
@@ -84,6 +84,7 @@ const {
FiltersPanel: StylesFiltersPanel,
ImageSettingsPanel,
AdvancedPanel: StylesAdvancedPanel,
+ useGlobalStyleLinks,
} = unlock( blockEditorPrivateApis );
function ScreenBlock( { name, variation } ) {
@@ -103,6 +104,7 @@ 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 (
@@ -311,10 +313,7 @@ function ScreenBlock( { name, variation } ) {
onChange={ setStyle }
settings={ settings }
defaultValues={ BACKGROUND_BLOCK_DEFAULT_VALUES }
- defaultControls={
- blockType?.supports?.background
- ?.__experimentalDefaultControls
- }
+ themeFileURIs={ _links?.[ 'wp:theme-file' ] }
/>
) }
diff --git a/packages/edit-site/src/components/global-styles/style-variations-container.js b/packages/edit-site/src/components/global-styles/style-variations-container.js
index aba86183ebb60..18fc6f6ad9056 100644
--- a/packages/edit-site/src/components/global-styles/style-variations-container.js
+++ b/packages/edit-site/src/components/global-styles/style-variations-container.js
@@ -110,6 +110,10 @@ export default function StyleVariationsContainer( { gap = 2 } ) {
];
}, [ fullStyleVariations, userStyles?.blocks, userStyles?.css ] );
+ if ( ! fullStyleVariations || fullStyleVariations?.length < 1 ) {
+ return null;
+ }
+
return (
+
) }
-
+
);
}
diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js
index 7f5eb3879bd80..5b1b10993ae59 100644
--- a/packages/edit-site/src/components/post-list/index.js
+++ b/packages/edit-site/src/components/post-list/index.js
@@ -372,15 +372,14 @@ export default function PostList( { postType } ) {
const { getEntityRecord, getPostType, canUser } =
select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );
- const postTypeObject = getPostType( postType );
return {
frontPageId: siteSettings?.page_on_front,
postsPageId: siteSettings?.page_for_posts,
labels: getPostType( postType )?.labels,
- canCreateRecord: canUser(
- 'create',
- postTypeObject?.rest_base || 'posts'
- ),
+ canCreateRecord: canUser( 'create', {
+ kind: 'postType',
+ name: postType,
+ } ),
};
},
[ postType ]
diff --git a/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js b/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
index 60f56e7e9072e..a4aa81e88393a 100644
--- a/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
+++ b/packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
@@ -375,7 +375,7 @@ function PushChangesToGlobalStyles( props ) {
const withPushChangesToGlobalStyles = createHigherOrderComponent(
( BlockEdit ) => ( props ) => (
<>
-
+
{ props.isSelected && }
>
)
diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js
index c28dfd8c9ba70..d3920f1103a33 100644
--- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js
+++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js
@@ -48,7 +48,10 @@ export default function WidgetAreasBlockEditorProvider( {
} = useSelect( ( select ) => {
const { canUser, getEntityRecord, getEntityRecords } =
select( coreStore );
- const siteSettings = canUser( 'read', 'settings' )
+ const siteSettings = canUser( 'read', {
+ kind: 'root',
+ name: 'site',
+ } )
? getEntityRecord( 'root', 'site' )
: undefined;
return {
diff --git a/packages/edit-widgets/src/filters/move-to-widget-area.js b/packages/edit-widgets/src/filters/move-to-widget-area.js
index c39315505232e..61a8b4cd7187f 100644
--- a/packages/edit-widgets/src/filters/move-to-widget-area.js
+++ b/packages/edit-widgets/src/filters/move-to-widget-area.js
@@ -48,7 +48,7 @@ const withMoveToWidgetAreaToolbarItem = createHigherOrderComponent(
return (
<>
-
+
{ isMoveToWidgetAreaVisible && (
{
'Tags help users and search engines navigate your site and find your content. Add a few keywords to describe your post.'
) }
-
+
);
};
diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js
index 37cea02b0f0f9..5f581e898c953 100644
--- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js
+++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js
@@ -5,6 +5,7 @@ import { __, _x, sprintf } from '@wordpress/i18n';
import { useEffect, useMemo, useState } from '@wordpress/element';
import { FormTokenField, withFilters } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
+import deprecated from '@wordpress/deprecated';
import { store as coreStore } from '@wordpress/core-data';
import { useDebounce } from '@wordpress/compose';
import { speak } from '@wordpress/a11y';
@@ -55,16 +56,28 @@ const termNamesToIds = ( names, terms ) => {
/**
* Renders a flat term selector component.
*
- * @param {Object} props The component props.
- * @param {string} props.slug The slug of the taxonomy.
+ * @param {Object} props The component props.
+ * @param {string} props.slug The slug of the taxonomy.
+ * @param {boolean} props.__nextHasNoMarginBottom Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 7.0. (The prop can be safely removed once this happens.)
*
* @return {JSX.Element} The rendered flat term selector component.
*/
-export function FlatTermSelector( { slug } ) {
+export function FlatTermSelector( { slug, __nextHasNoMarginBottom } ) {
const [ values, setValues ] = useState( [] );
const [ search, setSearch ] = useState( '' );
const debouncedSearch = useDebounce( setSearch, 500 );
+ if ( ! __nextHasNoMarginBottom ) {
+ deprecated(
+ 'Bottom margin styles for wp.editor.PostTaxonomiesFlatTermSelector',
+ {
+ 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.',
+ }
+ );
+ }
+
const {
terms,
termIds,
@@ -290,6 +303,7 @@ export function FlatTermSelector( { slug } ) {
removed: termRemovedLabel,
remove: removeTermLabel,
} }
+ __nextHasNoMarginBottom={ __nextHasNoMarginBottom }
/>
>
diff --git a/packages/editor/src/components/post-taxonomies/index.js b/packages/editor/src/components/post-taxonomies/index.js
index d96027a918c18..dc2345fd6197f 100644
--- a/packages/editor/src/components/post-taxonomies/index.js
+++ b/packages/editor/src/components/post-taxonomies/index.js
@@ -32,10 +32,17 @@ export function PostTaxonomies( { taxonomyWrapper = identity } ) {
const TaxonomyComponent = taxonomy.hierarchical
? HierarchicalTermSelector
: FlatTermSelector;
+ const taxonomyComponentProps = {
+ slug: taxonomy.slug,
+ ...( taxonomy.hierarchical
+ ? {}
+ : { __nextHasNoMarginBottom: true } ),
+ };
+
return (
{ taxonomyWrapper(
- ,
+ ,
taxonomy
) }
diff --git a/packages/editor/src/components/post-template/block-theme.js b/packages/editor/src/components/post-template/block-theme.js
index 189b967e2cd1c..7fcc5ead14f5b 100644
--- a/packages/editor/src/components/post-template/block-theme.js
+++ b/packages/editor/src/components/post-template/block-theme.js
@@ -53,7 +53,11 @@ export default function BlockThemeControl( { id } ) {
const canCreateTemplate = useSelect(
( select ) =>
- select( coreStore ).canUser( 'create', 'templates' ) ?? false
+ !! select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ),
+ []
);
if ( ! hasResolved ) {
diff --git a/packages/editor/src/components/post-template/classic-theme.js b/packages/editor/src/components/post-template/classic-theme.js
index 877b3bc3c53dc..276c6454fef09 100644
--- a/packages/editor/src/components/post-template/classic-theme.js
+++ b/packages/editor/src/components/post-template/classic-theme.js
@@ -33,8 +33,10 @@ function PostTemplateToggle( { isOpen, onClick } ) {
return availableTemplates[ templateSlug ];
}
const template =
- select( coreStore ).canUser( 'create', 'templates' ) &&
- select( editorStore ).getCurrentTemplateId();
+ select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ) && select( editorStore ).getCurrentTemplateId();
return (
template?.title ||
template?.slug ||
@@ -78,7 +80,10 @@ function PostTemplateDropdownContent( { onClose } ) {
( select ) => {
const { canUser, getEntityRecords } = select( coreStore );
const editorSettings = select( editorStore ).getEditorSettings();
- const canCreateTemplates = canUser( 'create', 'templates' );
+ const canCreateTemplates = canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } );
const _currentTemplateId =
select( editorStore ).getCurrentTemplateId();
return {
diff --git a/packages/editor/src/components/post-template/create-new-template.js b/packages/editor/src/components/post-template/create-new-template.js
index 04a7ab8febdff..7cf071ae028e9 100644
--- a/packages/editor/src/components/post-template/create-new-template.js
+++ b/packages/editor/src/components/post-template/create-new-template.js
@@ -17,7 +17,10 @@ export default function CreateNewTemplate( { onClick } ) {
const { canCreateTemplates } = useSelect( ( select ) => {
const { canUser } = select( coreStore );
return {
- canCreateTemplates: canUser( 'create', 'templates' ),
+ canCreateTemplates: canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ),
};
}, [] );
const [ isCreateModalOpen, setIsCreateModalOpen ] = useState( false );
diff --git a/packages/editor/src/components/post-template/panel.js b/packages/editor/src/components/post-template/panel.js
index 3986ff43afb0e..b5f0d34197c68 100644
--- a/packages/editor/src/components/post-template/panel.js
+++ b/packages/editor/src/components/post-template/panel.js
@@ -48,12 +48,20 @@ export default function PostTemplatePanel() {
}
const canCreateTemplates =
- select( coreStore ).canUser( 'create', 'templates' ) ?? false;
+ select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ) ?? false;
return canCreateTemplates;
}, [] );
const canViewTemplates = useSelect( ( select ) => {
- return select( coreStore ).canUser( 'read', 'templates' ) ?? false;
+ return (
+ select( coreStore ).canUser( 'read', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ) ?? false
+ );
}, [] );
if ( ( ! isBlockTheme || ! canViewTemplates ) && isVisible ) {
diff --git a/packages/editor/src/components/post-trash/check.js b/packages/editor/src/components/post-trash/check.js
index abb3b4381d95c..8f51df175c898 100644
--- a/packages/editor/src/components/post-trash/check.js
+++ b/packages/editor/src/components/post-trash/check.js
@@ -21,13 +21,17 @@ export default function PostTrashCheck( { children } ) {
const { canTrashPost } = useSelect( ( select ) => {
const { isEditedPostNew, getCurrentPostId, getCurrentPostType } =
select( editorStore );
- const { getPostType, canUser } = select( coreStore );
- const postType = getPostType( getCurrentPostType() );
+ const { canUser } = select( coreStore );
+ const postType = getCurrentPostType();
const postId = getCurrentPostId();
const isNew = isEditedPostNew();
- const resource = postType?.rest_base || ''; // eslint-disable-line camelcase
- const canUserDelete =
- postId && resource ? canUser( 'delete', resource, postId ) : false;
+ const canUserDelete = !! postId
+ ? canUser( 'delete', {
+ kind: 'postType',
+ name: postType,
+ id: postId,
+ } )
+ : false;
return {
canTrashPost: ( ! isNew || postId ) && canUserDelete,
diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js
index 4c68fd7390078..231e2df8f91e4 100644
--- a/packages/editor/src/components/provider/use-block-editor-settings.js
+++ b/packages/editor/src/components/provider/use-block-editor-settings.js
@@ -133,7 +133,10 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) {
const { getBlockTypes } = select( blocksStore );
const { getBlocksByName, getBlockAttributes } =
select( blockEditorStore );
- const siteSettings = canUser( 'read', 'settings' )
+ const siteSettings = canUser( 'read', {
+ kind: 'root',
+ name: 'site',
+ } )
? getEntityRecord( 'root', 'site' )
: undefined;
@@ -167,8 +170,15 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) {
hiddenBlockTypes: get( 'core', 'hiddenBlockTypes' ),
isDistractionFree: get( 'core', 'distractionFree' ),
keepCaretInsideBlock: get( 'core', 'keepCaretInsideBlock' ),
- hasUploadPermissions: canUser( 'create', 'media' ) ?? true,
- userCanCreatePages: canUser( 'create', 'pages' ),
+ hasUploadPermissions:
+ canUser( 'create', {
+ kind: 'root',
+ name: 'media',
+ } ) ?? true,
+ userCanCreatePages: canUser( 'create', {
+ kind: 'postType',
+ name: 'page',
+ } ),
pageOnFront: siteSettings?.page_on_front,
pageForPosts: siteSettings?.page_for_posts,
userPatternCategories: getUserPatternCategories(),
diff --git a/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js b/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js
index 775ef0f521363..449834e3ab61c 100644
--- a/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js
+++ b/packages/editor/src/components/visual-editor/edit-template-blocks-notification.js
@@ -40,7 +40,11 @@ export default function EditTemplateBlocksNotification( { contentRef } ) {
const canEditTemplate = useSelect(
( select ) =>
- select( coreStore ).canUser( 'create', 'templates' ) ?? false
+ !! select( coreStore ).canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } ),
+ []
);
const [ isDialogOpen, setIsDialogOpen ] = useState( false );
diff --git a/packages/editor/src/components/visual-editor/index.js b/packages/editor/src/components/visual-editor/index.js
index 5bf23958f1e7b..17f983293f1d4 100644
--- a/packages/editor/src/components/visual-editor/index.js
+++ b/packages/editor/src/components/visual-editor/index.js
@@ -142,7 +142,10 @@ function VisualEditor( {
const editorSettings = getEditorSettings();
const supportsTemplateMode = editorSettings.supportsTemplateMode;
const postTypeObject = getPostType( postTypeSlug );
- const canEditTemplate = canUser( 'create', 'templates' );
+ const canEditTemplate = canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } );
const currentTemplateId = getCurrentTemplateId();
const template = currentTemplateId
? getEditedEntityRecord(
diff --git a/packages/editor/src/hooks/pattern-overrides.js b/packages/editor/src/hooks/pattern-overrides.js
index 426bc4d357f0b..6f81f368351f3 100644
--- a/packages/editor/src/hooks/pattern-overrides.js
+++ b/packages/editor/src/hooks/pattern-overrides.js
@@ -41,7 +41,7 @@ const withPatternOverrideControls = createHigherOrderComponent(
return (
<>
-
+
{ props.isSelected && isSupportedBlock && (
) }
diff --git a/packages/interface/src/components/complementary-area/style.scss b/packages/interface/src/components/complementary-area/style.scss
index c15be5678a446..f45273516f53b 100644
--- a/packages/interface/src/components/complementary-area/style.scss
+++ b/packages/interface/src/components/complementary-area/style.scss
@@ -33,7 +33,7 @@
}
}
- p:not(.components-base-control__help) {
+ p:not(.components-base-control__help, .components-form-token-field__help) {
margin-top: 0;
}
diff --git a/packages/patterns/src/components/pattern-convert-button.js b/packages/patterns/src/components/pattern-convert-button.js
index 1db406601898b..d670cd85946aa 100644
--- a/packages/patterns/src/components/pattern-convert-button.js
+++ b/packages/patterns/src/components/pattern-convert-button.js
@@ -86,7 +86,10 @@ export default function PatternConvertButton( {
) &&
// Hide when current doesn't have permission to do that.
// Blocks refers to the wp_block post type, this checks the ability to create a post of that type.
- !! canUser( 'create', 'blocks' );
+ !! canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_block',
+ } );
return _canConvert;
},
diff --git a/packages/patterns/src/components/patterns-manage-button.js b/packages/patterns/src/components/patterns-manage-button.js
index bab9cab11462a..d350df62cbd00 100644
--- a/packages/patterns/src/components/patterns-manage-button.js
+++ b/packages/patterns/src/components/patterns-manage-button.js
@@ -28,16 +28,19 @@ function PatternsManageButton( { clientId } ) {
isVisible:
!! reusableBlock &&
isReusableBlock( reusableBlock ) &&
- !! canUser(
- 'update',
- 'blocks',
- reusableBlock.attributes.ref
- ),
+ !! canUser( 'update', {
+ kind: 'postType',
+ name: 'wp_block',
+ id: reusableBlock.attributes.ref,
+ } ),
innerBlockCount: getBlockCount( clientId ),
// The site editor and templates both check whether the user
// has edit_theme_options capabilities. We can leverage that here
// and omit the manage patterns link if the user can't access it.
- managePatternsUrl: canUser( 'create', 'templates' )
+ managePatternsUrl: canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } )
? addQueryArgs( 'site-editor.php', {
path: '/patterns',
} )
diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js
index 8cc892b6a84e6..2f9fcabafc119 100644
--- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js
+++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js
@@ -93,7 +93,10 @@ export default function ReusableBlockConvertButton( {
) &&
// Hide when current doesn't have permission to do that.
// Blocks refers to the wp_block post type, this checks the ability to create a post of that type.
- !! canUser( 'create', 'blocks' );
+ !! canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_block',
+ } );
return _canConvert;
},
diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js
index c0138517400fb..47047fcc561e4 100644
--- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js
+++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-blocks-manage-button.js
@@ -27,16 +27,19 @@ function ReusableBlocksManageButton( { clientId } ) {
isVisible:
!! reusableBlock &&
isReusableBlock( reusableBlock ) &&
- !! canUser(
- 'update',
- 'blocks',
- reusableBlock.attributes.ref
- ),
+ !! canUser( 'update', {
+ kind: 'postType',
+ name: 'wp_block',
+ id: reusableBlock.attributes.ref,
+ } ),
innerBlockCount: getBlockCount( clientId ),
// The site editor and templates both check whether the user
// has edit_theme_options capabilities. We can leverage that here
// and omit the manage patterns link if the user can't access it.
- managePatternsUrl: canUser( 'create', 'templates' )
+ managePatternsUrl: canUser( 'create', {
+ kind: 'postType',
+ name: 'wp_template',
+ } )
? addQueryArgs( 'site-editor.php', {
path: '/patterns',
} )
diff --git a/packages/scripts/package.json b/packages/scripts/package.json
index b0a9eb957983c..6d735745977da 100644
--- a/packages/scripts/package.json
+++ b/packages/scripts/package.json
@@ -92,7 +92,7 @@
"webpack-dev-server": "^4.15.1"
},
"peerDependencies": {
- "@playwright/test": "^1.43.0",
+ "@playwright/test": "^1.45.1",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
diff --git a/packages/scripts/scripts/check-licenses.js b/packages/scripts/scripts/check-licenses.js
index 5e0dd37239612..6401fd83e3482 100644
--- a/packages/scripts/scripts/check-licenses.js
+++ b/packages/scripts/scripts/check-licenses.js
@@ -179,8 +179,8 @@ const child = spawn.sync(
'--json',
'--long',
'--all',
- ...( prod ? [ '--prod' ] : [] ),
- ...( dev ? [ '--dev' ] : [] ),
+ ...( prod ? [ '--omit=dev' ] : [] ),
+ ...( dev ? [ '--include=dev' ] : [] ),
],
/*
* Set the max buffer to ~157MB, since the output size for
@@ -198,10 +198,14 @@ function traverseDepTree( deps ) {
const dep = deps[ key ];
if ( ignored.includes( dep.name ) ) {
- return;
+ continue;
+ }
+
+ if ( Object.keys( dep ).length === 0 ) {
+ continue;
}
- if ( ! dep.hasOwnProperty( 'path' ) ) {
+ if ( ! dep.hasOwnProperty( 'path' ) && ! dep.missing ) {
if ( dep.hasOwnProperty( 'peerMissing' ) ) {
process.stdout.write(
`${ WARNING } Unable to locate path for missing peer dep ${ dep.name }@${ dep.version }. `
@@ -213,17 +217,15 @@ function traverseDepTree( deps ) {
);
}
} else if ( dep.missing ) {
- process.stdout.write(
- `${ WARNING } missing dep ${ dep.name }@${ dep.version }. `
- );
+ for ( const problem of dep.problems ) {
+ process.stdout.write( `${ WARNING } ${ problem }.\n` );
+ }
} else {
checkDepLicense( dep.path );
}
if ( dep.hasOwnProperty( 'dependencies' ) ) {
traverseDepTree( dep.dependencies );
- } else {
- return;
}
}
}
@@ -272,6 +274,8 @@ function detectTypeFromLicenseText( licenseText ) {
);
}
+const reportedPackages = new Set();
+
function checkDepLicense( path ) {
if ( ! path ) {
return;
@@ -333,7 +337,7 @@ function checkDepLicense( path ) {
}
let detectedLicenseTypes = [ detectedLicenseType ];
- if ( detectedLicenseType.includes( ' AND ' ) ) {
+ if ( detectedLicenseType && detectedLicenseType.includes( ' AND ' ) ) {
detectedLicenseTypes = detectedLicenseType
.replace( /^\(*/g, '' )
.replace( /\)*$/, '' )
@@ -345,6 +349,13 @@ function checkDepLicense( path ) {
return;
}
+ // Do not report same package twice.
+ if ( reportedPackages.has( packageInfo.name ) ) {
+ return;
+ }
+
+ reportedPackages.add( packageInfo.name );
+
process.exitCode = 1;
process.stdout.write(
`${ ERROR } Module ${ packageInfo.name } has an incompatible license '${ licenseType }'.\n`
diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php
index 272c12705bf88..02186fecdcea2 100644
--- a/packages/style-engine/class-wp-style-engine.php
+++ b/packages/style-engine/class-wp-style-engine.php
@@ -42,31 +42,37 @@ final class WP_Style_Engine {
*/
const BLOCK_STYLE_DEFINITIONS_METADATA = array(
'background' => array(
- 'backgroundImage' => array(
+ 'backgroundImage' => array(
'property_keys' => array(
'default' => 'background-image',
),
'value_func' => array( self::class, 'get_url_or_value_css_declaration' ),
'path' => array( 'background', 'backgroundImage' ),
),
- 'backgroundPosition' => array(
+ 'backgroundPosition' => array(
'property_keys' => array(
'default' => 'background-position',
),
'path' => array( 'background', 'backgroundPosition' ),
),
- 'backgroundRepeat' => array(
+ 'backgroundRepeat' => array(
'property_keys' => array(
'default' => 'background-repeat',
),
'path' => array( 'background', 'backgroundRepeat' ),
),
- 'backgroundSize' => array(
+ 'backgroundSize' => array(
'property_keys' => array(
'default' => 'background-size',
),
'path' => array( 'background', 'backgroundSize' ),
),
+ 'backgroundAttachment' => array(
+ 'property_keys' => array(
+ 'default' => 'background-attachment',
+ ),
+ 'path' => array( 'background', 'backgroundAttachment' ),
+ ),
),
'color' => array(
'text' => array(
diff --git a/packages/style-engine/src/styles/background/index.ts b/packages/style-engine/src/styles/background/index.ts
index 6e79636cfda12..211b97343d89c 100644
--- a/packages/style-engine/src/styles/background/index.ts
+++ b/packages/style-engine/src/styles/background/index.ts
@@ -74,9 +74,22 @@ const backgroundSize = {
},
};
+const backgroundAttachment = {
+ name: 'backgroundAttachment',
+ generate: ( style: Style, options: StyleOptions ) => {
+ return generateRule(
+ style,
+ options,
+ [ 'background', 'backgroundAttachment' ],
+ 'backgroundAttachment'
+ );
+ },
+};
+
export default [
backgroundImage,
backgroundPosition,
backgroundRepeat,
backgroundSize,
+ backgroundAttachment,
];
diff --git a/packages/style-engine/src/test/index.js b/packages/style-engine/src/test/index.js
index fbccc8c48bc92..f7960e19187a2 100644
--- a/packages/style-engine/src/test/index.js
+++ b/packages/style-engine/src/test/index.js
@@ -231,6 +231,7 @@ describe( 'getCSSRules', () => {
backgroundPosition: '50% 50%',
backgroundRepeat: 'no-repeat',
backgroundSize: '300px',
+ backgroundAttachment: 'fixed',
},
color: {
text: '#dddddd',
@@ -399,6 +400,11 @@ describe( 'getCSSRules', () => {
key: 'backgroundSize',
value: '300px',
},
+ {
+ selector: '.some-selector',
+ key: 'backgroundAttachment',
+ value: 'fixed',
+ },
] );
} );
diff --git a/phpunit/block-supports/background-test.php b/phpunit/block-supports/background-test.php
index 165a65204793d..33e72e9b07010 100644
--- a/phpunit/block-supports/background-test.php
+++ b/phpunit/block-supports/background-test.php
@@ -149,21 +149,22 @@ public function data_background_block_support() {
'expected_wrapper' => 'Content
',
'wrapper' => 'Content
',
),
- 'background image style with contain, position, and repeat is applied' => array(
+ 'background image style with contain, position, attachment, and repeat is applied' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
'block_name' => 'test/background-rules-are-output',
'background_settings' => array(
'backgroundImage' => true,
),
'background_style' => array(
- 'backgroundImage' => array(
+ 'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
),
- 'backgroundRepeat' => 'no-repeat',
- 'backgroundSize' => 'contain',
+ '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(
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index 06892aab2e45a..d76517091f44f 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -4765,12 +4765,13 @@ public function test_get_top_level_background_image_styles() {
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => array(
'background' => array(
- 'backgroundImage' => array(
+ 'backgroundImage' => array(
'url' => 'http://example.org/image.png',
),
- 'backgroundSize' => 'contain',
- 'backgroundRepeat' => 'no-repeat',
- 'backgroundPosition' => 'center center',
+ 'backgroundSize' => 'contain',
+ 'backgroundRepeat' => 'no-repeat',
+ 'backgroundPosition' => 'center center',
+ 'backgroundAttachment' => 'fixed',
),
),
)
@@ -4781,7 +4782,7 @@ public function test_get_top_level_background_image_styles() {
'selector' => 'body',
);
- $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}";
+ $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;background-attachment: fixed;}";
$this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_styles_for_block()" with top-level background styles do not match expectations' );
$theme_json = new WP_Theme_JSON_Gutenberg(
@@ -4789,16 +4790,17 @@ public function test_get_top_level_background_image_styles() {
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => array(
'background' => array(
- 'backgroundImage' => "url('http://example.org/image.png')",
- 'backgroundSize' => 'contain',
- 'backgroundRepeat' => 'no-repeat',
- 'backgroundPosition' => 'center center',
+ 'backgroundImage' => "url('http://example.org/image.png')",
+ 'backgroundSize' => 'contain',
+ 'backgroundRepeat' => 'no-repeat',
+ 'backgroundPosition' => 'center center',
+ 'backgroundAttachment' => 'fixed',
),
),
)
);
- $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}";
+ $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;background-attachment: fixed;}";
$this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_styles_for_block()" with top-level background image as string type do not match expectations' );
}
@@ -4810,10 +4812,11 @@ public function test_get_block_background_image_styles() {
'blocks' => array(
'core/group' => array(
'background' => array(
- 'backgroundImage' => "url('http://example.org/group.png')",
- 'backgroundSize' => 'cover',
- 'backgroundRepeat' => 'no-repeat',
- 'backgroundPosition' => 'center center',
+ 'backgroundImage' => "url('http://example.org/group.png')",
+ 'backgroundSize' => 'cover',
+ 'backgroundRepeat' => 'no-repeat',
+ 'backgroundPosition' => 'center center',
+ 'backgroundAttachment' => 'fixed',
),
),
'core/quote' => array(
@@ -4852,7 +4855,7 @@ public function test_get_block_background_image_styles() {
),
);
- $group_styles = ":root :where(.wp-block-group){background-image: url('http://example.org/group.png');background-position: center center;background-repeat: no-repeat;background-size: cover;}";
+ $group_styles = ":root :where(.wp-block-group){background-image: url('http://example.org/group.png');background-position: center center;background-repeat: no-repeat;background-size: cover;background-attachment: fixed;}";
$this->assertSame( $group_styles, $theme_json->get_styles_for_block( $group_node ), 'Styles returned from "::get_styles_for_block()" with block-level background styles as string type do not match expectations' );
}
diff --git a/phpunit/style-engine/style-engine-test.php b/phpunit/style-engine/style-engine-test.php
index 63e5b3c1c7f7f..fdf5ccebabef9 100644
--- a/phpunit/style-engine/style-engine-test.php
+++ b/phpunit/style-engine/style-engine-test.php
@@ -506,22 +506,24 @@ public function data_wp_style_engine_get_styles() {
'inline_background_image_url_with_background_size' => array(
'block_styles' => array(
'background' => array(
- 'backgroundImage' => array(
+ 'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
),
- 'backgroundPosition' => 'center',
- 'backgroundRepeat' => 'no-repeat',
- 'backgroundSize' => 'cover',
+ 'backgroundPosition' => 'center',
+ 'backgroundRepeat' => 'no-repeat',
+ 'backgroundSize' => 'cover',
+ 'backgroundAttachment' => 'fixed',
),
),
'options' => array(),
'expected_output' => array(
- 'css' => "background-image:url('https://example.com/image.jpg');background-position:center;background-repeat:no-repeat;background-size:cover;",
+ 'css' => "background-image:url('https://example.com/image.jpg');background-position:center;background-repeat:no-repeat;background-size:cover;background-attachment:fixed;",
'declarations' => array(
- 'background-image' => "url('https://example.com/image.jpg')",
- 'background-position' => 'center',
- 'background-repeat' => 'no-repeat',
- 'background-size' => 'cover',
+ 'background-image' => "url('https://example.com/image.jpg')",
+ 'background-position' => 'center',
+ 'background-repeat' => 'no-repeat',
+ 'background-size' => 'cover',
+ 'background-attachment' => 'fixed',
),
),
),
diff --git a/schemas/json/theme.json b/schemas/json/theme.json
index f1666253e3de3..607228d65b80e 100644
--- a/schemas/json/theme.json
+++ b/schemas/json/theme.json
@@ -1220,6 +1220,17 @@
"$ref": "#/definitions/refComplete"
}
]
+ },
+ "backgroundAttachment": {
+ "description": "Sets the `background-attachment` CSS property.",
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/definitions/refComplete"
+ }
+ ]
}
},
"additionalProperties": false
diff --git a/test/e2e/specs/editor/various/font-appearance-control.spec.js b/test/e2e/specs/editor/various/font-appearance-control.spec.js
new file mode 100644
index 0000000000000..4b148b6dd8587
--- /dev/null
+++ b/test/e2e/specs/editor/various/font-appearance-control.spec.js
@@ -0,0 +1,90 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'Font Appearance Control dropdown menu', () => {
+ test.beforeEach( async ( { admin } ) => {
+ await admin.createNewPost();
+ } );
+
+ test( 'should apply available font weight and styles from active font family', async ( {
+ editor,
+ page,
+ } ) => {
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Regular',
+ style: {
+ typography: { fontWeight: '400', fontStyle: 'normal' },
+ },
+ },
+ } );
+ await page
+ .getByRole( 'button', { name: 'Typography options' } )
+ .click();
+ await expect(
+ page.getByRole( 'combobox', { name: 'Appearance' } )
+ ).toHaveText( 'Regular' );
+
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Extra Light Italic',
+ style: {
+ typography: { fontWeight: '200', fontStyle: 'italic' },
+ },
+ },
+ } );
+ await page
+ .getByRole( 'button', { name: 'Typography options' } )
+ .click();
+ await expect(
+ page.getByRole( 'combobox', { name: 'Appearance' } )
+ ).toHaveText( 'Extra Light Italic' );
+
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Bold Italic',
+ style: {
+ typography: { fontWeight: '700', fontStyle: 'italic' },
+ },
+ },
+ } );
+ await page
+ .getByRole( 'button', { name: 'Typography options' } )
+ .click();
+ await expect(
+ page.getByRole( 'combobox', { name: 'Appearance' } )
+ ).toHaveText( 'Bold Italic' );
+ } );
+
+ test( 'should apply Default appearance if weight and style are invalid', async ( {
+ editor,
+ page,
+ } ) => {
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Default',
+ style: {
+ typography: {
+ fontWeight: '',
+ fontStyle: 'invalid-style',
+ },
+ },
+ },
+ } );
+ await page
+ .getByRole( 'button', { name: 'Typography options' } )
+ .click();
+ await page
+ .getByRole( 'menuitemcheckbox', { name: 'Show Appearance' } )
+ .click();
+ await expect(
+ page.getByRole( 'combobox', { name: 'Appearance' } )
+ ).toHaveText( 'Default' );
+ } );
+} );
diff --git a/test/e2e/specs/editor/various/is-typing.spec.js b/test/e2e/specs/editor/various/is-typing.spec.js
index 8063f688409c4..e2c65f01928e0 100644
--- a/test/e2e/specs/editor/various/is-typing.spec.js
+++ b/test/e2e/specs/editor/various/is-typing.spec.js
@@ -53,6 +53,9 @@ test.describe( 'isTyping', () => {
.getByRole( 'button', { name: 'Title & Date' } )
.click();
+ await editor.openDocumentSettingsSidebar();
+ await page.getByLabel( 'Inherit query from template' ).click();
+
// Moving the mouse shows the toolbar.
await editor.showBlockToolbar();
// Open the dropdown.
diff --git a/test/e2e/specs/editor/various/rich-text.spec.js b/test/e2e/specs/editor/various/rich-text.spec.js
index aa56639281b49..29b4fb3d58901 100644
--- a/test/e2e/specs/editor/various/rich-text.spec.js
+++ b/test/e2e/specs/editor/various/rich-text.spec.js
@@ -119,7 +119,7 @@ test.describe( 'RichText (@firefox, @webkit)', () => {
expect( count ).toBe( 1 );
} );
- test( 'should return focus when pressing formatting button', async ( {
+ test( 'should return focus when pressing formatting button (-firefox)', async ( {
page,
editor,
} ) => {
@@ -415,7 +415,7 @@ test.describe( 'RichText (@firefox, @webkit)', () => {
] );
} );
- test( 'should update internal selection after fresh focus', async ( {
+ test( 'should update internal selection after fresh focus (-firefox)', async ( {
page,
editor,
pageUtils,
diff --git a/test/e2e/specs/site-editor/navigation-editor.spec.js b/test/e2e/specs/site-editor/navigation-editor.spec.js
index 2813ceb13748a..64a80e814d629 100644
--- a/test/e2e/specs/site-editor/navigation-editor.spec.js
+++ b/test/e2e/specs/site-editor/navigation-editor.spec.js
@@ -62,11 +62,9 @@ test.describe( 'Editing Navigation Menus', () => {
await expect( navBlockNode ).toBeVisible();
// The Navigation block description should contain the locked state information.
- const navBlockNodeDescriptionId =
- await navBlockNode.getAttribute( 'aria-describedby' );
- await expect(
- listView.locator( `id=${ navBlockNodeDescriptionId }` )
- ).toHaveText( /This block is locked./ );
+ await expect( navBlockNode ).toHaveAccessibleDescription(
+ /This block is locked./
+ );
// The block should have no options menu.
await expect(
diff --git a/test/integration/fixtures/blocks/core__categories.json b/test/integration/fixtures/blocks/core__categories.json
index 013418065bbbb..5d271aa5c8499 100644
--- a/test/integration/fixtures/blocks/core__categories.json
+++ b/test/integration/fixtures/blocks/core__categories.json
@@ -7,7 +7,8 @@
"showHierarchy": false,
"showPostCounts": false,
"showOnlyTopLevel": false,
- "showEmpty": false
+ "showEmpty": false,
+ "showLabel": true
},
"innerBlocks": []
}
diff --git a/test/integration/helpers/integration-test-editor.js b/test/integration/helpers/integration-test-editor.js
index 1317dec7b9226..bc2e6f71a954f 100644
--- a/test/integration/helpers/integration-test-editor.js
+++ b/test/integration/helpers/integration-test-editor.js
@@ -20,6 +20,11 @@ import {
unregisterBlockType,
getBlockTypes,
} from '@wordpress/blocks';
+import {
+ store as richTextStore,
+ unregisterFormatType,
+} from '@wordpress/rich-text';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -58,14 +63,18 @@ export async function selectBlock( name ) {
export function Editor( { testBlocks, settings = {} } ) {
const [ currentBlocks, updateBlocks ] = useState( testBlocks );
+ const { getFormatTypes } = useSelect( richTextStore );
useEffect( () => {
return () => {
getBlockTypes().forEach( ( { name } ) =>
unregisterBlockType( name )
);
+ getFormatTypes().forEach( ( { name } ) =>
+ unregisterFormatType( name )
+ );
};
- }, [] );
+ }, [ getFormatTypes ] );
return (