diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md
index c58b8b3239f33e..768103bf39ebe1 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-living.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-living.md
@@ -72,7 +72,7 @@ Settings related to shadows.
| Property | Type | Default | Props |
| --- | --- | --- |--- |
-| defaultPresets | boolean | true | |
+| defaultPresets | boolean | false | |
| presets | array | | name, shadow, slug |
---
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 0d2520ff9682a4..6bce853ba2ec2c 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -670,6 +670,7 @@ public static function get_element_class_name( $element ) {
array( 'spacing', 'margin' ),
array( 'spacing', 'padding' ),
array( 'typography', 'lineHeight' ),
+ array( 'shadow', 'defaultPresets' ),
);
/**
diff --git a/lib/theme.json b/lib/theme.json
index c2ed7fdca39ed5..5f261e2c120043 100644
--- a/lib/theme.json
+++ b/lib/theme.json
@@ -191,7 +191,7 @@
"text": true
},
"shadow": {
- "defaultPresets": true,
+ "defaultPresets": false,
"presets": [
{
"name": "Natural",
diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss
index b988c0499f1fb8..41ef8c0e13cbc6 100644
--- a/packages/base-styles/_mixins.scss
+++ b/packages/base-styles/_mixins.scss
@@ -297,6 +297,16 @@
}
}
}
+
+ &[aria-disabled="true"],
+ &:disabled {
+ background: $gray-100;
+ border-color: $gray-300;
+ cursor: default;
+
+ // Override style inherited from wp-admin. Required to avoid degraded appearance on different backgrounds.
+ opacity: 1;
+ }
}
@mixin radio-control {
diff --git a/packages/block-editor/src/components/block-edit/context.js b/packages/block-editor/src/components/block-edit/context.js
index b280cc9c51f6b8..e8480912a87f03 100644
--- a/packages/block-editor/src/components/block-edit/context.js
+++ b/packages/block-editor/src/components/block-edit/context.js
@@ -6,6 +6,7 @@ import { createContext, useContext } from '@wordpress/element';
export const mayDisplayControlsKey = Symbol( 'mayDisplayControls' );
export const mayDisplayParentControlsKey = Symbol( 'mayDisplayParentControls' );
export const blockEditingModeKey = Symbol( 'blockEditingMode' );
+export const blockBindingsKey = Symbol( 'blockBindings' );
export const DEFAULT_BLOCK_EDIT_CONTEXT = {
name: '',
diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js
index 457cd919f89381..4e94a8a427510d 100644
--- a/packages/block-editor/src/components/block-edit/index.js
+++ b/packages/block-editor/src/components/block-edit/index.js
@@ -14,6 +14,7 @@ import {
mayDisplayControlsKey,
mayDisplayParentControlsKey,
blockEditingModeKey,
+ blockBindingsKey,
} from './context';
/**
@@ -41,7 +42,8 @@ export default function BlockEdit( {
attributes = {},
__unstableLayoutClassNames,
} = props;
- const { layout = null } = attributes;
+ const { layout = null, metadata = {} } = attributes;
+ const { bindings } = metadata;
const layoutSupport =
hasBlockSupport( name, 'layout', false ) ||
hasBlockSupport( name, '__experimentalLayout', false );
@@ -62,6 +64,7 @@ export default function BlockEdit( {
[ mayDisplayControlsKey ]: mayDisplayControls,
[ mayDisplayParentControlsKey ]: mayDisplayParentControls,
[ blockEditingModeKey ]: blockEditingMode,
+ [ blockBindingsKey ]: bindings,
} ),
[
name,
@@ -73,6 +76,7 @@ export default function BlockEdit( {
mayDisplayControls,
mayDisplayParentControls,
blockEditingMode,
+ bindings,
]
) }
>
diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js
index e8df38232306cf..43b3c4dc09327d 100644
--- a/packages/block-editor/src/components/block-inspector/index.js
+++ b/packages/block-editor/src/components/block-inspector/index.js
@@ -30,7 +30,7 @@ import PositionControls from '../inspector-controls-tabs/position-controls-panel
import useBlockInspectorAnimationSettings from './useBlockInspectorAnimationSettings';
import BlockInfo from '../block-info-slot-fill';
import BlockQuickNavigation from '../block-quick-navigation';
-import { getBorderPanelLabel } from '../../hooks/border';
+import { useBorderPanelLabel } from '../../hooks/border';
function BlockInspectorLockedBlocks( { topLevelLockedBlock } ) {
const contentClientIds = useSelect(
@@ -116,6 +116,10 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
selectedBlockClientId
);
+ const borderPanelLabel = useBorderPanelLabel( {
+ blockName: selectedBlockName,
+ } );
+
if ( count > 1 ) {
return (
@@ -140,9 +144,7 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
/>
>
@@ -251,7 +253,7 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => {
[ blockName ]
);
const blockInformation = useBlockDisplayInformation( clientId );
- const borderPanelLabel = getBorderPanelLabel( { blockName } );
+ const borderPanelLabel = useBorderPanelLabel( { blockName } );
return (
diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js
index 0fb7f55b9955d2..245d0ee4348b2e 100644
--- a/packages/block-editor/src/components/block-preview/index.js
+++ b/packages/block-editor/src/components/block-preview/index.js
@@ -54,7 +54,11 @@ export function BlockPreview( {
[]
);
const settings = useMemo(
- () => ( { ...originalSettings, __unstableIsPreviewMode: true } ),
+ () => ( {
+ ...originalSettings,
+ focusMode: false, // Disable "Spotlight mode".
+ __unstableIsPreviewMode: true,
+ } ),
[ originalSettings ]
);
const renderedBlocks = useMemo(
@@ -117,6 +121,7 @@ export function useBlockPreview( { blocks, props = {}, layout } ) {
() => ( {
...originalSettings,
styles: undefined, // Clear styles included by the parent settings, as they are already output by the parent's EditorStyles.
+ focusMode: false, // Disable "Spotlight mode".
__unstableIsPreviewMode: true,
} ),
[ originalSettings ]
diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss
index 81dbb98528a84b..102594c004aff6 100644
--- a/packages/block-editor/src/components/block-toolbar/style.scss
+++ b/packages/block-editor/src/components/block-toolbar/style.scss
@@ -207,17 +207,18 @@
}
}
- .block-editor-block-mover .block-editor-block-mover__move-button-container {
- width: auto;
-
- @include break-small() {
+ .block-editor-block-mover {
+ .block-editor-block-mover__move-button-container {
+ width: auto;
position: relative;
+ }
- &::before {
+ &:not(.is-horizontal) .block-editor-block-mover__move-button-container::before {
+ @include break-small() {
content: "";
height: $border-width;
width: 100%;
- background: $gray-900;
+ background: $gray-200;
position: absolute;
top: 50%;
left: 50%;
@@ -226,6 +227,10 @@
transform: translate(-50%, 0);
margin-top: -$border-width * 0.5;
}
+
+ @include break-medium {
+ background: $gray-900;
+ }
}
}
diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js
index f8144f1545aebe..04bb5f9d15b1ad 100644
--- a/packages/block-editor/src/components/global-styles/border-panel.js
+++ b/packages/block-editor/src/components/global-styles/border-panel.js
@@ -21,23 +21,24 @@ import { useColorsPerOrigin } from './hooks';
import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils';
import { overrideOrigins } from '../../store/get-block-settings';
import { setImmutably } from '../../utils/object';
-import { getBorderPanelLabel } from '../../hooks/border';
-import { ShadowPopover } from './shadow-panel-components';
+import { useBorderPanelLabel } from '../../hooks/border';
+import { ShadowPopover, useShadowPresets } from './shadow-panel-components';
-function useHasShadowControl( settings ) {
- return !! settings?.shadow;
+export function useHasBorderPanel( settings ) {
+ const controls = Object.values( useHasBorderPanelControls( settings ) );
+ return controls.some( Boolean );
}
-export function useHasBorderPanel( settings ) {
- const controls = [
- useHasBorderColorControl( settings ),
- useHasBorderRadiusControl( settings ),
- useHasBorderStyleControl( settings ),
- useHasBorderWidthControl( settings ),
- useHasShadowControl( settings ),
- ];
+export function useHasBorderPanelControls( settings ) {
+ const controls = {
+ hasBorderColor: useHasBorderColorControl( settings ),
+ hasBorderRadius: useHasBorderRadiusControl( settings ),
+ hasBorderStyle: useHasBorderStyleControl( settings ),
+ hasBorderWidth: useHasBorderWidthControl( settings ),
+ hasShadow: useHasShadowControl( settings ),
+ };
- return controls.some( Boolean );
+ return controls;
}
function useHasBorderColorControl( settings ) {
@@ -56,6 +57,11 @@ function useHasBorderWidthControl( settings ) {
return settings?.border?.width;
}
+function useHasShadowControl( settings ) {
+ const shadows = useShadowPresets( settings );
+ return !! settings?.shadow && shadows.length > 0;
+}
+
function BorderToolsPanel( {
resetAllFilter,
onChange,
@@ -215,14 +221,16 @@ export default function BorderPanel( {
const showBorderByDefault =
defaultControls?.color || defaultControls?.width;
- const label = getBorderPanelLabel( {
+ const hasBorderControl =
+ showBorderColor ||
+ showBorderStyle ||
+ showBorderWidth ||
+ showBorderRadius;
+
+ const label = useBorderPanelLabel( {
blockName: name,
hasShadowControl,
- hasBorderControl:
- showBorderColor ||
- showBorderStyle ||
- showBorderWidth ||
- showBorderRadius,
+ hasBorderControl,
} );
return (
@@ -280,9 +288,12 @@ export default function BorderPanel( {
isShownByDefault={ defaultControls.shadow }
panelId={ panelId }
>
-
- { __( 'Shadow' ) }
-
+ { hasBorderControl ? (
+
+ { __( 'Shadow' ) }
+
+ ) : null }
+
@@ -37,42 +45,76 @@ export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) {
activeShadow={ shadow }
onSelect={ onShadowChange }
/>
+
+
+
);
}
export function ShadowPresets( { presets, activeShadow, onSelect } ) {
+ const { CompositeV2: Composite, useCompositeStoreV2: useCompositeStore } =
+ unlock( componentsPrivateApis );
+ const compositeStore = useCompositeStore();
return ! presets ? null : (
-
+
{ presets.map( ( { name, slug, shadow } ) => (
onSelect( shadow === activeShadow ? undefined : shadow )
}
shadow={ shadow }
/>
) ) }
-
+
);
}
-export function ShadowIndicator( { label, isActive, onSelect, shadow } ) {
+export function ShadowIndicator( { type, label, isActive, onSelect, shadow } ) {
+ const { CompositeItemV2: CompositeItem } = unlock( componentsPrivateApis );
return (
-
-
-
+
+ { isActive && }
+
+ }
+ />
);
}
@@ -123,3 +165,30 @@ function renderShadowToggle() {
);
};
}
+
+export function useShadowPresets( settings ) {
+ return useMemo( () => {
+ if ( ! settings?.shadow ) {
+ return EMPTY_ARRAY;
+ }
+
+ const defaultPresetsEnabled = settings?.shadow?.defaultPresets;
+ const { default: defaultShadows, theme: themeShadows } =
+ settings?.shadow?.presets ?? {};
+ const unsetShadow = {
+ name: __( 'Unset' ),
+ slug: 'unset',
+ shadow: 'none',
+ };
+
+ const shadowPresets = [
+ ...( ( defaultPresetsEnabled && defaultShadows ) || EMPTY_ARRAY ),
+ ...( themeShadows || EMPTY_ARRAY ),
+ ];
+ if ( shadowPresets.length ) {
+ shadowPresets.unshift( unsetShadow );
+ }
+
+ return shadowPresets;
+ }, [ settings ] );
+}
diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss
index d2ba88f9f31e00..d357d2e65ab72a 100644
--- a/packages/block-editor/src/components/global-styles/style.scss
+++ b/packages/block-editor/src/components/global-styles/style.scss
@@ -2,10 +2,24 @@
fill: currentColor;
}
+// @todo: Ideally, popover, swatch size, and gap values should be CSS variables
+// to apply precise grid layouts.
+// https://github.com/WordPress/gutenberg/blob/954ecae571abbddc113d3a4bd8e1a72230180554/packages/block-editor/src/components/duotone-control/style.scss#L3-L9
.block-editor-global-styles__shadow-popover-container {
width: 230px;
}
+.block-editor-global-styles__shadow__list {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ padding-bottom: $grid-unit-10;
+}
+
+.block-editor-global-styles__clear-shadow {
+ text-align: right;
+}
+
.block-editor-global-styles-filters-panel__dropdown,
.block-editor-global-styles__shadow-dropdown {
display: block;
@@ -21,14 +35,6 @@
}
}
-// wrapper to clip the shadow beyond 6px
-.block-editor-global-styles__shadow-indicator-wrapper {
- padding: $grid-unit-15 * 0.5;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
// These styles are similar to the color palette.
.block-editor-global-styles__shadow-indicator {
color: $gray-800;
@@ -37,8 +43,25 @@
cursor: pointer;
padding: 0;
- height: $button-size-small;
- width: $button-size-small;
+ height: $button-size-small + 2 * $border-width;
+ width: $button-size-small + 2 * $border-width;
+ box-sizing: border-box;
+
+ transform: scale(1);
+ transition: transform 0.1s ease;
+ will-change: transform;
+
+ &:focus {
+ border: #{ $border-width * 2 } solid $gray-700;
+ }
+
+ &:hover {
+ transform: scale(1.2);
+ }
+
+ &.unset {
+ background: linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%);
+ }
}
.block-editor-global-styles-advanced-panel__custom-css-input textarea {
diff --git a/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js
index 6c2556f2378ff1..cdbd24e6eb144a 100644
--- a/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js
+++ b/packages/block-editor/src/components/inspector-controls-tabs/styles-tab.js
@@ -11,10 +11,10 @@ import { __ } from '@wordpress/i18n';
import BlockStyles from '../block-styles';
import DefaultStylePicker from '../default-style-picker';
import InspectorControls from '../inspector-controls';
-import { getBorderPanelLabel } from '../../hooks/border';
+import { useBorderPanelLabel } from '../../hooks/border';
const StylesTab = ( { blockName, clientId, hasBlockStyles } ) => {
- const borderPanelLabel = getBorderPanelLabel( { blockName } );
+ const borderPanelLabel = useBorderPanelLabel( { blockName } );
return (
<>
diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js
index 88a19cc4c7af49..d922187ec3aa0d 100644
--- a/packages/block-editor/src/components/link-control/link-preview.js
+++ b/packages/block-editor/src/components/link-control/link-preview.js
@@ -16,8 +16,9 @@ import { useCopyToClipboard } from '@wordpress/compose';
import { filterURLForDisplay, safeDecodeURI } from '@wordpress/url';
import { Icon, globe, info, linkOff, edit, copySmall } from '@wordpress/icons';
import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
-import { useDispatch } from '@wordpress/data';
+import { useDispatch, useSelect } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
+import { store as preferencesStore } from '@wordpress/preferences';
/**
* Internal dependencies
@@ -33,6 +34,12 @@ export default function LinkPreview( {
hasUnlinkControl = false,
onRemove,
} ) {
+ const showIconLabels = useSelect(
+ ( select ) =>
+ select( preferencesStore ).get( 'core', 'showIconLabels' ),
+ []
+ );
+
// Avoid fetching if rich previews are not desired.
const showRichPreviews = hasRichPreviews ? value?.url : null;
@@ -139,7 +146,7 @@ export default function LinkPreview( {
label={ sprintf(
// Translators: %s is a placeholder for the link URL and an optional colon, (if a Link URL is present).
__( 'Copy link%s' ), // Ends up looking like "Copy link: https://example.com".
- isEmptyURL ? '' : ': ' + value.url
+ isEmptyURL || showIconLabels ? '' : ': ' + value.url
) }
ref={ ref }
disabled={ isEmptyURL }
diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss
index 425df96ab31fa6..9531ad6f0c7a09 100644
--- a/packages/block-editor/src/components/link-control/style.scss
+++ b/packages/block-editor/src/components/link-control/style.scss
@@ -34,6 +34,15 @@ $block-editor-link-control-number-of-actions: 1;
content: attr(aria-label);
}
}
+
+ .block-editor-link-control__search-item-top {
+ gap: $grid-unit-10;
+
+ .components-button.has-icon {
+ min-width: inherit;
+ width: min-content;
+ }
+ }
}
}
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index bac2559ffbba9b..aa870dee073977 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -19,13 +19,14 @@ import {
removeFormat,
} from '@wordpress/rich-text';
import { Popover } from '@wordpress/components';
-import { getBlockType } from '@wordpress/blocks';
+import { getBlockType, store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { useBlockEditorAutocompleteProps } from '../autocomplete';
import { useBlockEditContext } from '../block-edit';
+import { blockBindingsKey } from '../block-edit/context';
import FormatToolbarContainer from './format-toolbar-container';
import { store as blockEditorStore } from '../../store';
import { useUndoAutomaticChange } from './use-undo-automatic-change';
@@ -109,6 +110,7 @@ export function RichTextWrapper(
__unstableDisableFormats: disableFormats,
disableLineBreaks,
__unstableAllowPrefixTransformations,
+ disableEditing,
...props
},
forwardedRef
@@ -116,11 +118,9 @@ export function RichTextWrapper(
props = removeNativeProps( props );
const anchorRef = useRef();
- const {
- clientId,
- isSelected: isBlockSelected,
- name: blockName,
- } = useBlockEditContext();
+ const context = useBlockEditContext();
+ const { clientId, isSelected: isBlockSelected, name: blockName } = context;
+ const blockBindings = context[ blockBindingsKey ];
const selector = ( select ) => {
// Avoid subscribing to the block editor store if the block is not
// selected.
@@ -128,12 +128,10 @@ export function RichTextWrapper(
return { isSelected: false };
}
- const { getSelectionStart, getSelectionEnd, getBlockAttributes } =
+ const { getSelectionStart, getSelectionEnd } =
select( blockEditorStore );
const selectionStart = getSelectionStart();
const selectionEnd = getSelectionEnd();
- const blockBindings =
- getBlockAttributes( clientId )?.metadata?.bindings;
let isSelected;
@@ -146,50 +144,60 @@ export function RichTextWrapper(
isSelected = selectionStart.clientId === clientId;
}
- // Disable Rich Text editing if block bindings specify that.
- let shouldDisableEditing = false;
- if ( blockBindings && blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS ) {
- const blockTypeAttributes = getBlockType( blockName ).attributes;
- const { getBlockBindingsSource } = unlock(
- select( blockEditorStore )
- );
- for ( const [ attribute, args ] of Object.entries(
- blockBindings
- ) ) {
- if (
- blockTypeAttributes?.[ attribute ]?.source !== 'rich-text'
- ) {
- break;
- }
-
- // If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it.
- const blockBindingsSource = getBlockBindingsSource(
- args.source
- );
- if (
- ! blockBindingsSource ||
- blockBindingsSource.lockAttributesEditing
- ) {
- shouldDisableEditing = true;
- break;
- }
- }
- }
-
return {
selectionStart: isSelected ? selectionStart.offset : undefined,
selectionEnd: isSelected ? selectionEnd.offset : undefined,
isSelected,
- shouldDisableEditing,
};
};
- const { selectionStart, selectionEnd, isSelected, shouldDisableEditing } =
- useSelect( selector, [
- clientId,
- identifier,
- originalIsSelected,
- isBlockSelected,
- ] );
+ const { selectionStart, selectionEnd, isSelected } = useSelect( selector, [
+ clientId,
+ identifier,
+ originalIsSelected,
+ isBlockSelected,
+ ] );
+
+ const disableBoundBlocks = useSelect(
+ ( select ) => {
+ // Disable Rich Text editing if block bindings specify that.
+ let _disableBoundBlocks = false;
+ if ( blockBindings && blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS ) {
+ const blockTypeAttributes =
+ getBlockType( blockName ).attributes;
+ const { getBlockBindingsSource } = unlock(
+ select( blocksStore )
+ );
+ for ( const [ attribute, args ] of Object.entries(
+ blockBindings
+ ) ) {
+ if (
+ blockTypeAttributes?.[ attribute ]?.source !==
+ 'rich-text'
+ ) {
+ break;
+ }
+
+ // If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it.
+ const blockBindingsSource = getBlockBindingsSource(
+ args.source
+ );
+ if (
+ ! blockBindingsSource ||
+ blockBindingsSource.lockAttributesEditing
+ ) {
+ _disableBoundBlocks = true;
+ break;
+ }
+ }
+ }
+
+ return _disableBoundBlocks;
+ },
+ [ blockBindings, blockName ]
+ );
+
+ const shouldDisableEditing = disableEditing || disableBoundBlocks;
+
const { getSelectionStart, getSelectionEnd, getBlockRootClientId } =
useSelect( blockEditorStore );
const { selectionChange } = useDispatch( blockEditorStore );
@@ -442,19 +450,34 @@ export function RichTextWrapper(
);
}
-const ForwardedRichTextContainer = withDeprecations(
+// This is the private API for the RichText component.
+// It allows access to all props, not just the public ones.
+export const PrivateRichText = withDeprecations(
forwardRef( RichTextWrapper )
);
-ForwardedRichTextContainer.Content = Content;
-ForwardedRichTextContainer.isEmpty = ( value ) => {
+PrivateRichText.Content = Content;
+PrivateRichText.isEmpty = ( value ) => {
return ! value || value.length === 0;
};
+// This is the public API for the RichText component.
+// We wrap the PrivateRichText component to hide some props from the public API.
/**
* @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/rich-text/README.md
*/
-export default ForwardedRichTextContainer;
+const PublicForwardedRichTextContainer = forwardRef( ( props, ref ) => {
+ return (
+
+ );
+} );
+
+PublicForwardedRichTextContainer.Content = Content;
+PublicForwardedRichTextContainer.isEmpty = ( value ) => {
+ return ! value || value.length === 0;
+};
+
+export default PublicForwardedRichTextContainer;
export { RichTextShortcut } from './shortcut';
export { RichTextToolbarButton } from './toolbar-button';
export { __unstableRichTextInputEvent } from './input-event';
diff --git a/packages/block-editor/src/components/rich-text/use-enter.js b/packages/block-editor/src/components/rich-text/use-enter.js
index 4daf70e7fa3c74..6b40a82d72d4b2 100644
--- a/packages/block-editor/src/components/rich-text/use-enter.js
+++ b/packages/block-editor/src/components/rich-text/use-enter.js
@@ -21,6 +21,10 @@ export function useEnter( props ) {
propsRef.current = props;
return useRefEffect( ( element ) => {
function onKeyDown( event ) {
+ if ( event.target.contentEditable !== 'true' ) {
+ return;
+ }
+
if ( event.defaultPrevented ) {
return;
}
diff --git a/packages/block-editor/src/components/url-popover/style.scss b/packages/block-editor/src/components/url-popover/style.scss
index df4e10fe13d534..324d82d4183aab 100644
--- a/packages/block-editor/src/components/url-popover/style.scss
+++ b/packages/block-editor/src/components/url-popover/style.scss
@@ -58,6 +58,7 @@
text-overflow: ellipsis;
white-space: nowrap;
margin-right: $grid-unit-10;
+ min-width: 150px;
// Avoids the popover from growing too wide when the URL is long.
// See https://github.com/WordPress/gutenberg/issues/58599
max-width: $modal-min-width;
diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js
index 1e0b8e894d2067..eb84352ab62f09 100644
--- a/packages/block-editor/src/hooks/block-hooks.js
+++ b/packages/block-editor/src/hooks/block-hooks.js
@@ -35,12 +35,12 @@ function BlockHooksControlPure( { name, clientId } ) {
const { blockIndex, rootClientId, innerBlocksLength } = useSelect(
( select ) => {
- const { getBlock, getBlockIndex, getBlockRootClientId } =
+ const { getBlocks, getBlockIndex, getBlockRootClientId } =
select( blockEditorStore );
return {
blockIndex: getBlockIndex( clientId ),
- innerBlocksLength: getBlock( clientId )?.innerBlocks?.length,
+ innerBlocksLength: getBlocks( clientId )?.length,
rootClientId: getBlockRootClientId( clientId ),
};
},
@@ -49,7 +49,7 @@ function BlockHooksControlPure( { name, clientId } ) {
const hookedBlockClientIds = useSelect(
( select ) => {
- const { getBlock, getGlobalBlockCount } =
+ const { getBlocks, getGlobalBlockCount } =
select( blockEditorStore );
const _hookedBlockClientIds = hookedBlocksForCurrentBlock.reduce(
@@ -69,7 +69,7 @@ function BlockHooksControlPure( { name, clientId } ) {
// Any of the current block's siblings (with the right block type) qualifies
// as a hooked block (inserted `before` or `after` the current one), as the block
// might've been automatically inserted and then moved around a bit by the user.
- candidates = getBlock( rootClientId )?.innerBlocks;
+ candidates = getBlocks( rootClientId );
break;
case 'first_child':
@@ -77,7 +77,7 @@ function BlockHooksControlPure( { name, clientId } ) {
// Any of the current block's child blocks (with the right block type) qualifies
// as a hooked first or last child block, as the block might've been automatically
// inserted and then moved around a bit by the user.
- candidates = getBlock( clientId ).innerBlocks;
+ candidates = getBlocks( clientId );
break;
}
@@ -161,6 +161,11 @@ function BlockHooksControlPure( { name, clientId } ) {
title={ __( 'Plugins' ) }
initialOpen={ true }
>
+
+ { __(
+ 'Manage the inclusion of blocks added automatically by plugins.'
+ ) }
+
{ Object.keys( groupedHookedBlocks ).map( ( vendor ) => {
return (
diff --git a/packages/block-editor/src/hooks/block-hooks.scss b/packages/block-editor/src/hooks/block-hooks.scss
index dc05d6e6947bb0..4d871233de482b 100644
--- a/packages/block-editor/src/hooks/block-hooks.scss
+++ b/packages/block-editor/src/hooks/block-hooks.scss
@@ -13,4 +13,10 @@
.components-toggle-control .components-h-stack .components-h-stack {
flex-direction: row;
}
+
+ .block-editor-hooks__block-hooks-helptext {
+ color: $gray-700;
+ font-size: $helptext-font-size;
+ margin-bottom: $grid-unit-20;
+ }
}
diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js
index c743b457fc05cc..a8ed9bccaf7df9 100644
--- a/packages/block-editor/src/hooks/border.js
+++ b/packages/block-editor/src/hooks/border.js
@@ -18,9 +18,14 @@ import { useSelect } from '@wordpress/data';
import { getColorClassName } from '../components/colors';
import InspectorControls from '../components/inspector-controls';
import useMultipleOriginColorsAndGradients from '../components/colors-gradients/use-multiple-origin-colors-and-gradients';
-import { cleanEmptyObject, shouldSkipSerialization } from './utils';
+import {
+ cleanEmptyObject,
+ shouldSkipSerialization,
+ useBlockSettings,
+} from './utils';
import {
useHasBorderPanel,
+ useHasBorderPanelControls,
BorderPanel as StylesBorderPanel,
} from '../components/global-styles';
import { store as blockEditorStore } from '../store';
@@ -220,14 +225,21 @@ export function hasShadowSupport( blockName ) {
return hasBlockSupport( blockName, SHADOW_SUPPORT_KEY );
}
-export function getBorderPanelLabel( {
+export function useBorderPanelLabel( {
blockName,
hasBorderControl,
hasShadowControl,
} = {} ) {
+ const settings = useBlockSettings( blockName );
+ const controls = useHasBorderPanelControls( settings );
+
if ( ! hasBorderControl && ! hasShadowControl && blockName ) {
- hasBorderControl = hasBorderSupport( blockName );
- hasShadowControl = hasShadowSupport( blockName );
+ hasBorderControl =
+ controls?.hasBorderColor ||
+ controls?.hasBorderStyle ||
+ controls?.hasBorderWidth ||
+ controls?.hasBorderRadius;
+ hasShadowControl = controls?.hasShadow;
}
if ( hasBorderControl && hasShadowControl ) {
diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js
index 899faf0a8cbd5d..0e5b6614f07cbf 100644
--- a/packages/block-editor/src/hooks/use-bindings-attributes.js
+++ b/packages/block-editor/src/hooks/use-bindings-attributes.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { getBlockType } from '@wordpress/blocks';
+import { getBlockType, store as blocksStore } from '@wordpress/blocks';
import { createHigherOrderComponent } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { addFilter } from '@wordpress/hooks';
@@ -33,18 +33,16 @@ const createEditFunctionWithBindingsAttribute = () =>
createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const { clientId, name: blockName } = useBlockEditContext();
- const { getBlockBindingsSource } = unlock(
- useSelect( blockEditorStore )
- );
+ const blockBindingsSources = unlock(
+ useSelect( blocksStore )
+ ).getAllBlockBindingsSources();
const { getBlockAttributes } = useSelect( blockEditorStore );
const updatedAttributes = getBlockAttributes( clientId );
if ( updatedAttributes?.metadata?.bindings ) {
Object.entries( updatedAttributes.metadata.bindings ).forEach(
( [ attributeName, settings ] ) => {
- const source = getBlockBindingsSource(
- settings.source
- );
+ const source = blockBindingsSources[ settings.source ];
if ( source && source.useSource ) {
// Second argument (`updateMetaValue`) will be used to update the value in the future.
diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js
index 220aa5f4127270..ec6843ead24895 100644
--- a/packages/block-editor/src/private-apis.js
+++ b/packages/block-editor/src/private-apis.js
@@ -27,6 +27,7 @@ import { ExperimentalBlockCanvas } from './components/block-canvas';
import { getDuotoneFilter } from './components/duotone/utils';
import { useFlashEditableBlocks } from './components/use-flash-editable-blocks';
import { selectBlockPatternsKey } from './store/private-keys';
+import { PrivateRichText } from './components/rich-text/';
/**
* Private @wordpress/block-editor APIs.
@@ -58,4 +59,5 @@ lock( privateApis, {
usesContextKey,
useFlashEditableBlocks,
selectBlockPatternsKey,
+ PrivateRichText,
} );
diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js
index ae0a06152fb933..e32e604a02b1e1 100644
--- a/packages/block-editor/src/store/private-actions.js
+++ b/packages/block-editor/src/store/private-actions.js
@@ -390,16 +390,6 @@ export function stopEditingAsBlocks( clientId ) {
};
}
-export function registerBlockBindingsSource( source ) {
- return {
- type: 'REGISTER_BLOCK_BINDINGS_SOURCE',
- sourceName: source.name,
- sourceLabel: source.label,
- useSource: source.useSource,
- lockAttributesEditing: source.lockAttributesEditing,
- };
-}
-
/**
* Returns an action object used in signalling that the user has begun to drag.
*
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index c885e43ba75208..e4885cbbd9e1e1 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -341,14 +341,6 @@ export function getLastFocus( state ) {
return state.lastFocus;
}
-export function getAllBlockBindingsSources( state ) {
- return state.blockBindingsSources;
-}
-
-export function getBlockBindingsSource( state, sourceName ) {
- return state.blockBindingsSources[ sourceName ];
-}
-
/**
* Returns true if the user is dragging anything, or false otherwise. It is possible for a
* user to be dragging data from outside of the editor, so this selector is separate from
diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index 1b535e51950164..12a2dc61d8269d 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -2050,20 +2050,6 @@ export function lastFocus( state = false, action ) {
return state;
}
-function blockBindingsSources( state = {}, action ) {
- if ( action.type === 'REGISTER_BLOCK_BINDINGS_SOURCE' ) {
- return {
- ...state,
- [ action.sourceName ]: {
- label: action.sourceLabel,
- useSource: action.useSource,
- lockAttributesEditing: action.lockAttributesEditing ?? true,
- },
- };
- }
- return state;
-}
-
const combinedReducers = combineReducers( {
blocks,
isDragging,
@@ -2095,7 +2081,6 @@ const combinedReducers = combineReducers( {
blockRemovalRules,
openedBlockSettingsMenu,
registeredInserterMediaCategories,
- blockBindingsSources,
} );
function withAutomaticChangeReset( reducer ) {
diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js
index ff90cdd1bf64c0..1a35cea9d8cc7d 100644
--- a/packages/block-library/src/button/edit.js
+++ b/packages/block-library/src/button/edit.js
@@ -45,6 +45,7 @@ import {
createBlock,
cloneBlock,
getDefaultBlockName,
+ store as blocksStore,
} from '@wordpress/blocks';
import { useMergeRefs, useRefEffect } from '@wordpress/compose';
import { useSelect, useDispatch } from '@wordpress/data';
@@ -239,7 +240,7 @@ function ButtonEdit( props ) {
}
const blockBindingsSource = unlock(
- select( blockEditorStore )
+ select( blocksStore )
).getBlockBindingsSource( metadata?.bindings?.url?.source );
return {
diff --git a/packages/block-library/src/buttons/transforms.js b/packages/block-library/src/buttons/transforms.js
index 3e89b4973e3728..9848299f3a99f9 100644
--- a/packages/block-library/src/buttons/transforms.js
+++ b/packages/block-library/src/buttons/transforms.js
@@ -4,6 +4,11 @@
import { createBlock } from '@wordpress/blocks';
import { __unstableCreateElement as createElement } from '@wordpress/rich-text';
+/**
+ * Internal dependencies
+ */
+import { getTransformedMetadata } from '../utils/get-transformed-metadata';
+
const transforms = {
from: [
{
@@ -33,10 +38,8 @@ const transforms = {
{},
// Loop the selected buttons.
buttons.map( ( attributes ) => {
- const element = createElement(
- document,
- attributes.content
- );
+ const { content, metadata } = attributes;
+ const element = createElement( document, content );
// Remove any HTML tags.
const text = element.innerText || '';
// Get first url.
@@ -46,6 +49,13 @@ const transforms = {
return createBlock( 'core/button', {
text,
url,
+ metadata: getTransformedMetadata(
+ metadata,
+ 'core/button',
+ ( { content: contentBinding } ) => ( {
+ text: contentBinding,
+ } )
+ ),
} );
} )
),
diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js
index af6d4686af8122..e537db342b8d5c 100644
--- a/packages/block-library/src/code/transforms.js
+++ b/packages/block-library/src/code/transforms.js
@@ -4,6 +4,11 @@
import { createBlock } from '@wordpress/blocks';
import { create, toHTMLString } from '@wordpress/rich-text';
+/**
+ * Internal dependencies
+ */
+import { getTransformedMetadata } from '../utils/get-transformed-metadata';
+
const transforms = {
from: [
{
@@ -14,17 +19,21 @@ const transforms = {
{
type: 'block',
blocks: [ 'core/paragraph' ],
- transform: ( { content } ) =>
- createBlock( 'core/code', { content } ),
+ transform: ( { content, metadata } ) =>
+ createBlock( 'core/code', {
+ content,
+ metadata: getTransformedMetadata( metadata, 'core/code' ),
+ } ),
},
{
type: 'block',
blocks: [ 'core/html' ],
- transform: ( { content: text } ) => {
+ transform: ( { content: text, metadata } ) => {
return createBlock( 'core/code', {
// The HTML is plain text (with plain line breaks), so
// convert it to rich text.
content: toHTMLString( { value: create( { text } ) } ),
+ metadata: getTransformedMetadata( metadata, 'core/code' ),
} );
},
},
@@ -51,8 +60,14 @@ const transforms = {
{
type: 'block',
blocks: [ 'core/paragraph' ],
- transform: ( { content } ) =>
- createBlock( 'core/paragraph', { content } ),
+ transform: ( { content, metadata } ) =>
+ createBlock( 'core/paragraph', {
+ content,
+ metadata: getTransformedMetadata(
+ metadata,
+ 'core/paragraph'
+ ),
+ } ),
},
],
};
diff --git a/packages/block-library/src/cover/edit/block-controls.js b/packages/block-library/src/cover/edit/block-controls.js
index 59aaaaffe77d75..c4137ad2a8409a 100644
--- a/packages/block-library/src/cover/edit/block-controls.js
+++ b/packages/block-library/src/cover/edit/block-controls.js
@@ -8,6 +8,7 @@ import {
MediaReplaceFlow,
__experimentalBlockAlignmentMatrixControl as BlockAlignmentMatrixControl,
__experimentalBlockFullHeightAligmentControl as FullHeightAlignmentControl,
+ privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
@@ -15,6 +16,9 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import { ALLOWED_MEDIA_TYPES } from '../shared';
+import { unlock } from '../../lock-unlock';
+
+const { cleanEmptyObject } = unlock( blockEditorPrivateApis );
export default function CoverBlockControls( {
attributes,
@@ -30,7 +34,10 @@ export default function CoverBlockControls( {
const [ prevMinHeightValue, setPrevMinHeightValue ] = useState( minHeight );
const [ prevMinHeightUnit, setPrevMinHeightUnit ] =
useState( minHeightUnit );
- const isMinFullHeight = minHeightUnit === 'vh' && minHeight === 100;
+ const isMinFullHeight =
+ minHeightUnit === 'vh' &&
+ minHeight === 100 &&
+ ! attributes?.style?.dimensions?.aspectRatio;
const toggleMinFullHeight = () => {
if ( isMinFullHeight ) {
// If there aren't previous values, take the default ones.
@@ -51,10 +58,17 @@ export default function CoverBlockControls( {
setPrevMinHeightValue( minHeight );
setPrevMinHeightUnit( minHeightUnit );
- // Set full height.
+ // Set full height, and clear any aspect ratio value.
return setAttributes( {
minHeight: 100,
minHeightUnit: 'vh',
+ style: cleanEmptyObject( {
+ ...attributes?.style,
+ dimensions: {
+ ...attributes?.style?.dimensions,
+ aspectRatio: undefined, // Reset aspect ratio when minHeight is set.
+ },
+ } ),
} );
};
diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js
index 889ce3716b9631..634e99dc2d98d6 100644
--- a/packages/block-library/src/cover/edit/inspector-controls.js
+++ b/packages/block-library/src/cover/edit/inspector-controls.js
@@ -308,7 +308,11 @@ export default function CoverInspectorControls( {
panelId={ clientId }
>
setAttributes( {
diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss
index 3b4eac41a0d3b4..837e3834e2e1ba 100644
--- a/packages/block-library/src/cover/style.scss
+++ b/packages/block-library/src/cover/style.scss
@@ -8,9 +8,10 @@
align-items: center;
padding: 1em;
// Prevent the `wp-block-cover__background` span from overflowing the container when border-radius is applied.
+ // `overflow: hidden` is provided as a fallback for browsers that don't support `overflow: clip`.
+ overflow: hidden;
// Use clip instead of overflow: hidden so that sticky position works on child elements.
- // Use overflow-x instead of overflow so that aspect-ratio allows content to expand the area of the cover block.
- overflow-x: clip;
+ overflow: clip;
// This block has customizable padding, border-box makes that more predictable.
box-sizing: border-box;
// Keep the flex layout direction to the physical direction (LTR) in RTL languages.
diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php
index 86eaf694add4ca..380c093c8117ed 100644
--- a/packages/block-library/src/footnotes/index.php
+++ b/packages/block-library/src/footnotes/index.php
@@ -105,7 +105,7 @@ function register_block_core_footnotes_post_meta() {
}
}
}
-/**
+/*
* Most post types are registered at priority 10, so use priority 20 here in
* order to catch them.
*/
diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js
index a4db7884620963..f040ff06e37e86 100644
--- a/packages/block-library/src/heading/transforms.js
+++ b/packages/block-library/src/heading/transforms.js
@@ -7,6 +7,7 @@ import { createBlock, getBlockAttributes } from '@wordpress/blocks';
* Internal dependencies
*/
import { getLevelFromHeadingNodeName } from './shared';
+import { getTransformedMetadata } from '../utils/get-transformed-metadata';
const transforms = {
from: [
@@ -15,12 +16,20 @@ const transforms = {
isMultiBlock: true,
blocks: [ 'core/paragraph' ],
transform: ( attributes ) =>
- attributes.map( ( { content, anchor, align: textAlign } ) =>
- createBlock( 'core/heading', {
- content,
- anchor,
- textAlign,
- } )
+ attributes.map(
+ ( { content, anchor, align: textAlign, metadata } ) =>
+ createBlock( 'core/heading', {
+ content,
+ anchor,
+ textAlign,
+ metadata: getTransformedMetadata(
+ metadata,
+ 'core/heading',
+ ( { content: contentBinding } ) => ( {
+ content: contentBinding,
+ } )
+ ),
+ } )
),
},
{
@@ -82,8 +91,18 @@ const transforms = {
isMultiBlock: true,
blocks: [ 'core/paragraph' ],
transform: ( attributes ) =>
- attributes.map( ( { content, textAlign: align } ) =>
- createBlock( 'core/paragraph', { content, align } )
+ attributes.map( ( { content, textAlign: align, metadata } ) =>
+ createBlock( 'core/paragraph', {
+ content,
+ align,
+ metadata: getTransformedMetadata(
+ metadata,
+ 'core/paragraph',
+ ( { content: contentBinding } ) => ( {
+ content: contentBinding,
+ } )
+ ),
+ } )
),
},
],
diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js
index 86970b588ff03d..e1e221bc9575a0 100644
--- a/packages/block-library/src/image/edit.js
+++ b/packages/block-library/src/image/edit.js
@@ -7,6 +7,7 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob';
+import { store as blocksStore } from '@wordpress/blocks';
import { Placeholder } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import {
@@ -342,7 +343,7 @@ export function ImageEdit( {
}
const blockBindingsSource = unlock(
- select( blockEditorStore )
+ select( blocksStore )
).getBlockBindingsSource( metadata?.bindings?.url?.source );
return {
diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js
index a0d481681ee934..ea0f82a2e1986b 100644
--- a/packages/block-library/src/image/image.js
+++ b/packages/block-library/src/image/image.js
@@ -33,7 +33,7 @@ import { useEffect, useMemo, useState, useRef } from '@wordpress/element';
import { __, _x, sprintf, isRTL } from '@wordpress/i18n';
import { DOWN } from '@wordpress/keycodes';
import { getFilename } from '@wordpress/url';
-import { switchToBlockType } from '@wordpress/blocks';
+import { switchToBlockType, store as blocksStore } from '@wordpress/blocks';
import { crop, overlayText, upload } from '@wordpress/icons';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';
@@ -411,14 +411,16 @@ export default function Image( {
lockHrefControls = false,
lockAltControls = false,
lockTitleControls = false,
+ lockCaption = false,
} = useSelect(
( select ) => {
if ( ! isSingleSelected ) {
return {};
}
-
- const { getBlockBindingsSource, getBlockParentsByBlockName } =
- unlock( select( blockEditorStore ) );
+ const { getBlockBindingsSource } = unlock( select( blocksStore ) );
+ const { getBlockParentsByBlockName } = unlock(
+ select( blockEditorStore )
+ );
const {
url: urlBinding,
alt: altBinding,
@@ -444,6 +446,10 @@ export default function Image( {
// Disable editing the link of the URL if the image is inside a pattern instance.
// This is a temporary solution until we support overriding the link on the frontend.
hasParentPattern,
+ lockCaption:
+ // Disable editing the caption if the image is inside a pattern instance.
+ // This is a temporary solution until we support overriding the caption on the frontend.
+ hasParentPattern,
lockAltControls:
!! altBinding &&
( ! altBindingSource ||
@@ -907,6 +913,7 @@ export default function Image( {
which causes duplicated image upload. */ }
{ ! temporaryURL && controls }
{ img }
+
>
);
diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php
index 5dcada62f6feb5..3a3a654aee6126 100644
--- a/packages/block-library/src/navigation/index.php
+++ b/packages/block-library/src/navigation/index.php
@@ -554,14 +554,18 @@ private static function get_nav_element_directives( $is_interactive ) {
// When adding to this array be mindful of security concerns.
$nav_element_context = data_wp_context(
array(
- 'overlayOpenedBy' => array(),
+ 'overlayOpenedBy' => array(
+ 'click' => false,
+ 'hover' => false,
+ 'focus' => false,
+ ),
'type' => 'overlay',
'roleAttribute' => '',
'ariaLabel' => __( 'Menu' ),
)
);
$nav_element_directives = '
- data-wp-interactive="core/navigation"'
+ data-wp-interactive="core/navigation" '
. $nav_element_context;
return $nav_element_directives;
@@ -764,7 +768,7 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut
) ) {
// Add directives to the parent ``.
$tags->set_attribute( 'data-wp-interactive', 'core/navigation' );
- $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": {}, "type": "submenu" }' );
+ $tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' );
$tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
$tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
$tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
diff --git a/packages/block-library/src/post-featured-image/edit.js b/packages/block-library/src/post-featured-image/edit.js
index 26f3439964f90e..a3b3b1ec6398a2 100644
--- a/packages/block-library/src/post-featured-image/edit.js
+++ b/packages/block-library/src/post-featured-image/edit.js
@@ -24,6 +24,7 @@ import {
useBlockProps,
store as blockEditorStore,
__experimentalUseBorderProps as useBorderProps,
+ useBlockEditingMode,
} from '@wordpress/block-editor';
import { useMemo } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
@@ -143,6 +144,7 @@ export default function PostFeaturedImageEdit( {
style: { width, height, aspectRatio },
} );
const borderProps = useBorderProps( attributes );
+ const blockEditingMode = useBlockEditingMode();
const placeholder = ( content ) => {
return (
@@ -174,8 +176,13 @@ export default function PostFeaturedImageEdit( {
createErrorNotice( message, { type: 'snackbar' } );
};
- const controls = (
+ const controls = blockEditingMode === 'default' && (
<>
+
>
);
+
let image;
/**
@@ -251,11 +259,6 @@ export default function PostFeaturedImageEdit( {
) : (
placeholder()
) }
-
>
);
@@ -360,11 +363,6 @@ export default function PostFeaturedImageEdit( {
) : (
image
) }
-
>
);
diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js
index 28d9b8cdcfd8ff..1cb89bc3e5e67b 100644
--- a/packages/block-library/src/post-title/edit.js
+++ b/packages/block-library/src/post-title/edit.js
@@ -115,53 +115,59 @@ export default function PostTitleEdit( {
return (
<>
{ blockEditingMode === 'default' && (
-
-
- setAttributes( { level: newLevel } )
- }
- />
- {
- setAttributes( { textAlign: nextAlign } );
- } }
- />
-
- ) }
-
-
- setAttributes( { isLink: ! isLink } ) }
- checked={ isLink }
- />
- { isLink && (
- <>
+ <>
+
+
+ setAttributes( { level: newLevel } )
+ }
+ />
+ {
+ setAttributes( { textAlign: nextAlign } );
+ } }
+ />
+
+
+
- setAttributes( {
- linkTarget: value ? '_blank' : '_self',
- } )
+ label={ __( 'Make title a link' ) }
+ onChange={ () =>
+ setAttributes( { isLink: ! isLink } )
}
- checked={ linkTarget === '_blank' }
+ checked={ isLink }
/>
-
- setAttributes( { rel: newRel } )
- }
- />
- >
- ) }
-
-
+ { isLink && (
+ <>
+
+ setAttributes( {
+ linkTarget: value
+ ? '_blank'
+ : '_self',
+ } )
+ }
+ checked={ linkTarget === '_blank' }
+ />
+
+ setAttributes( { rel: newRel } )
+ }
+ />
+ >
+ ) }
+
+
+ >
+ ) }
{ titleElement }
>
);
diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss
index 4e283530a0e277..1434f29de76810 100644
--- a/packages/block-library/src/search/style.scss
+++ b/packages/block-library/src/search/style.scss
@@ -12,6 +12,8 @@ $button-spacing-y: math.div($grid-unit-15, 2); // 6px
svg {
min-width: $grid-unit-30;
min-height: $grid-unit-30;
+ width: 1.25em;
+ height: 1.25em;
fill: currentColor;
vertical-align: text-bottom;
}
diff --git a/packages/block-library/src/utils/caption.js b/packages/block-library/src/utils/caption.js
index e9055cc29df02c..57f034c76c6ed9 100644
--- a/packages/block-library/src/utils/caption.js
+++ b/packages/block-library/src/utils/caption.js
@@ -10,14 +10,21 @@ import { useState, useEffect, useCallback } from '@wordpress/element';
import { usePrevious } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import {
- RichText,
BlockControls,
__experimentalGetElementClassName,
+ privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { ToolbarButton } from '@wordpress/components';
import { caption as captionIcon } from '@wordpress/icons';
import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../lock-unlock';
+
+const { PrivateRichText: RichText } = unlock( blockEditorPrivateApis );
+
export function Caption( {
key = 'caption',
attributes,
@@ -28,6 +35,7 @@ export function Caption( {
label = __( 'Caption text' ),
showToolbarButton = true,
className,
+ disableEditing,
} ) {
const caption = attributes[ key ];
const prevCaption = usePrevious( caption );
@@ -101,6 +109,7 @@ export function Caption( {
createBlock( getDefaultBlockName() )
)
}
+ disableEditing={ disableEditing }
/>
) }
>
diff --git a/packages/block-library/src/utils/get-transformed-metadata.js b/packages/block-library/src/utils/get-transformed-metadata.js
new file mode 100644
index 00000000000000..53d79d3c1e42ac
--- /dev/null
+++ b/packages/block-library/src/utils/get-transformed-metadata.js
@@ -0,0 +1,65 @@
+/**
+ * WordPress dependencies
+ */
+import { getBlockType } from '@wordpress/blocks';
+
+/**
+ * Transform the metadata attribute with only the values and bindings specified by each transform.
+ * Returns `undefined` if the input metadata is falsy.
+ *
+ * @param {Object} metadata Original metadata attribute from the block that is being transformed.
+ * @param {Object} newBlockName Name of the final block after the transformation.
+ * @param {Function} bindingsCallback Optional callback to transform the `bindings` property object.
+ * @return {Object|undefined} New metadata object only with the relevant properties.
+ */
+export function getTransformedMetadata(
+ metadata,
+ newBlockName,
+ bindingsCallback
+) {
+ if ( ! metadata ) {
+ return;
+ }
+ const { supports } = getBlockType( newBlockName );
+ // Fixed until an opt-in mechanism is implemented.
+ const BLOCK_BINDINGS_SUPPORTED_BLOCKS = [
+ 'core/paragraph',
+ 'core/heading',
+ 'core/image',
+ 'core/button',
+ ];
+ // The metadata properties that should be preserved after the transform.
+ const transformSupportedProps = [];
+ // If it support bindings, and there is a transform bindings callback, add the `id` and `bindings` properties.
+ if (
+ BLOCK_BINDINGS_SUPPORTED_BLOCKS.includes( newBlockName ) &&
+ bindingsCallback
+ ) {
+ transformSupportedProps.push( 'id', 'bindings' );
+ }
+ // If it support block naming (true by default), add the `name` property.
+ if ( supports.renaming !== false ) {
+ transformSupportedProps.push( 'name' );
+ }
+
+ // Return early if no supported properties.
+ if ( ! transformSupportedProps.length ) {
+ return;
+ }
+
+ const newMetadata = Object.entries( metadata ).reduce(
+ ( obj, [ prop, value ] ) => {
+ // If prop is not supported, don't add it to the new metadata object.
+ if ( ! transformSupportedProps.includes( prop ) ) {
+ return obj;
+ }
+ obj[ prop ] =
+ prop === 'bindings' ? bindingsCallback( value ) : value;
+ return obj;
+ },
+ {}
+ );
+
+ // Return undefined if object is empty.
+ return Object.keys( newMetadata ).length ? newMetadata : undefined;
+}
diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js
index bc06e231b17222..d609f70b91b55d 100644
--- a/packages/blocks/src/store/private-actions.js
+++ b/packages/blocks/src/store/private-actions.js
@@ -40,3 +40,18 @@ export function addUnprocessedBlockType( name, blockType ) {
dispatch.addBlockTypes( processedBlockType );
};
}
+
+/**
+ * Register new block bindings source.
+ *
+ * @param {string} source Name of the source to register.
+ */
+export function registerBlockBindingsSource( source ) {
+ return {
+ type: 'REGISTER_BLOCK_BINDINGS_SOURCE',
+ sourceName: source.name,
+ sourceLabel: source.label,
+ useSource: source.useSource,
+ lockAttributesEditing: source.lockAttributesEditing,
+ };
+}
diff --git a/packages/blocks/src/store/private-selectors.js b/packages/blocks/src/store/private-selectors.js
index f2acf9c1051846..e6eac8be421466 100644
--- a/packages/blocks/src/store/private-selectors.js
+++ b/packages/blocks/src/store/private-selectors.js
@@ -186,3 +186,26 @@ export function getBootstrappedBlockType( state, name ) {
export function getUnprocessedBlockTypes( state ) {
return state.unprocessedBlockTypes;
}
+
+/**
+ * Returns all the block bindings sources registered.
+ *
+ * @param {Object} state Data state.
+ *
+ * @return {Object} All the registered sources and their properties.
+ */
+export function getAllBlockBindingsSources( state ) {
+ return state.blockBindingsSources;
+}
+
+/**
+ * Returns a specific block bindings source.
+ *
+ * @param {Object} state Data state.
+ * @param {string} sourceName Name of the source to get.
+ *
+ * @return {Object} The specific block binding source and its properties.
+ */
+export function getBlockBindingsSource( state, sourceName ) {
+ return state.blockBindingsSources[ sourceName ];
+}
diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js
index 91f061871f9e27..f92fb376b530a7 100644
--- a/packages/blocks/src/store/reducer.js
+++ b/packages/blocks/src/store/reducer.js
@@ -383,6 +383,20 @@ export function collections( state = {}, action ) {
return state;
}
+export function blockBindingsSources( state = {}, action ) {
+ if ( action.type === 'REGISTER_BLOCK_BINDINGS_SOURCE' ) {
+ return {
+ ...state,
+ [ action.sourceName ]: {
+ label: action.sourceLabel,
+ useSource: action.useSource,
+ lockAttributesEditing: action.lockAttributesEditing ?? true,
+ },
+ };
+ }
+ return state;
+}
+
export default combineReducers( {
bootstrappedBlockTypes,
unprocessedBlockTypes,
@@ -395,4 +409,5 @@ export default combineReducers( {
groupingBlockName,
categories,
collections,
+ blockBindingsSources,
} );
diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js
index f59520f3c436bd..003d01e49641d9 100644
--- a/packages/core-data/src/resolvers.js
+++ b/packages/core-data/src/resolvers.js
@@ -661,12 +661,42 @@ export const getUserPatternCategories =
export const getNavigationFallbackId =
() =>
- async ( { dispatch } ) => {
+ async ( { dispatch, select } ) => {
const fallback = await apiFetch( {
- path: addQueryArgs( '/wp-block-editor/v1/navigation-fallback' ),
+ path: addQueryArgs( '/wp-block-editor/v1/navigation-fallback', {
+ _embed: true,
+ } ),
} );
+ const record = fallback?._embedded?.self;
+
dispatch.receiveNavigationFallbackId( fallback?.id );
+
+ if ( record ) {
+ // If the fallback is already in the store, don't invalidate navigation queries.
+ // Otherwise, invalidate the cache for the scenario where there were no Navigation
+ // posts in the state and the fallback created one.
+ const existingFallbackEntityRecord = select.getEntityRecord(
+ 'postType',
+ 'wp_navigation',
+ fallback.id
+ );
+ const invalidateNavigationQueries = ! existingFallbackEntityRecord;
+ dispatch.receiveEntityRecords(
+ 'postType',
+ 'wp_navigation',
+ record,
+ undefined,
+ invalidateNavigationQueries
+ );
+
+ // Resolve to avoid further network requests.
+ dispatch.finishResolution( 'getEntityRecord', [
+ 'postType',
+ 'wp_navigation',
+ fallback.id,
+ ] );
+ }
};
export const getDefaultTemplateId =
diff --git a/packages/dataviews/src/bulk-actions.js b/packages/dataviews/src/bulk-actions.js
index 9fd9f628286e09..5e4139fe0622e7 100644
--- a/packages/dataviews/src/bulk-actions.js
+++ b/packages/dataviews/src/bulk-actions.js
@@ -7,7 +7,7 @@ import {
Modal,
} from '@wordpress/components';
import { __, sprintf, _n } from '@wordpress/i18n';
-import { useMemo, useState, useCallback } from '@wordpress/element';
+import { useMemo, useState, useCallback, useEffect } from '@wordpress/element';
/**
* Internal dependencies
@@ -21,6 +21,24 @@ const {
DropdownMenuSeparatorV2: DropdownMenuSeparator,
} = unlock( componentsPrivateApis );
+export function useHasAPossibleBulkAction( actions, item ) {
+ return useMemo( () => {
+ return actions.some( ( action ) => {
+ return action.supportsBulk && action.isEligible( item );
+ } );
+ }, [ actions, item ] );
+}
+
+export function useSomeItemHasAPossibleBulkAction( actions, data ) {
+ return useMemo( () => {
+ return data.some( ( item ) => {
+ return actions.some( ( action ) => {
+ return action.supportsBulk && action.isEligible( item );
+ } );
+ } );
+ }, [ actions, data ] );
+}
+
function ActionWithModal( {
action,
selectedItems,
@@ -107,15 +125,47 @@ export default function BulkActions( {
() => actions.filter( ( action ) => action.supportsBulk ),
[ actions ]
);
- const areAllSelected = selection && selection.length === data.length;
const [ isMenuOpen, onMenuOpenChange ] = useState( false );
const [ actionWithModal, setActionWithModal ] = useState();
+ const selectableItems = useMemo( () => {
+ return data.filter( ( item ) => {
+ return bulkActions.some( ( action ) => action.isEligible( item ) );
+ } );
+ }, [ data, bulkActions ] );
+
+ const numberSelectableItems = selectableItems.length;
+ const areAllSelected =
+ selection && selection.length === numberSelectableItems;
+
const selectedItems = useMemo( () => {
return data.filter( ( item ) =>
selection.includes( getItemId( item ) )
);
}, [ selection, data, getItemId ] );
+ const hasNonSelectableItemSelected = useMemo( () => {
+ return selectedItems.some( ( item ) => {
+ return ! selectableItems.includes( item );
+ } );
+ }, [ selectedItems, selectableItems ] );
+ useEffect( () => {
+ if ( hasNonSelectableItemSelected ) {
+ onSelectionChange(
+ selectedItems.filter( ( selectedItem ) => {
+ return selectableItems.some( ( item ) => {
+ return getItemId( selectedItem ) === getItemId( item );
+ } );
+ } )
+ );
+ }
+ }, [
+ hasNonSelectableItemSelected,
+ selectedItems,
+ selectableItems,
+ getItemId,
+ onSelectionChange,
+ ] );
+
if ( bulkActions.length === 0 ) {
return null;
}
@@ -157,9 +207,9 @@ export default function BulkActions( {
disabled={ areAllSelected }
hideOnClick={ false }
onClick={ () => {
- onSelectionChange( data );
+ onSelectionChange( selectableItems );
} }
- suffix={ data.length }
+ suffix={ numberSelectableItems }
>
{ __( 'Select all' ) }
diff --git a/packages/dataviews/src/dataviews.js b/packages/dataviews/src/dataviews.js
index c7ae6b6e27aad8..9bcf531006406a 100644
--- a/packages/dataviews/src/dataviews.js
+++ b/packages/dataviews/src/dataviews.js
@@ -20,6 +20,16 @@ import BulkActions from './bulk-actions';
const defaultGetItemId = ( item ) => item.id;
const defaultOnSelectionChange = () => {};
+function useSomeItemHasAPossibleBulkAction( actions, data ) {
+ return useMemo( () => {
+ return data.some( ( item ) => {
+ return actions.some( ( action ) => {
+ return action.supportsBulk && action.isEligible( item );
+ } );
+ } );
+ }, [ actions, data ] );
+}
+
export default function DataViews( {
view,
onChangeView,
@@ -75,30 +85,49 @@ export default function DataViews( {
render: field.render || field.getValue,
} ) );
}, [ fields ] );
+
+ const hasPossibleBulkAction = useSomeItemHasAPossibleBulkAction(
+ actions,
+ data
+ );
return (
- { search && (
-
+ { search && (
+
+ ) }
+
- ) }
- { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) && (
-
- ) }
+
+ { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) &&
+ hasPossibleBulkAction && (
+
+ ) }
-
-
-
+ { filterComponents }
+
+ );
} );
export default Filters;
diff --git a/packages/dataviews/src/pagination.js b/packages/dataviews/src/pagination.js
index f69d7c7a12e193..4e44b5e9ca4b2e 100644
--- a/packages/dataviews/src/pagination.js
+++ b/packages/dataviews/src/pagination.js
@@ -27,7 +27,12 @@ const Pagination = memo( function Pagination( {
justify="end"
className="dataviews-pagination"
>
-
+
{ createInterpolateElement(
sprintf(
// translators: %s: Total number of pages.
diff --git a/packages/dataviews/src/single-selection-checkbox.js b/packages/dataviews/src/single-selection-checkbox.js
index 0aa27824c4f9b2..ce21419f969cee 100644
--- a/packages/dataviews/src/single-selection-checkbox.js
+++ b/packages/dataviews/src/single-selection-checkbox.js
@@ -11,6 +11,7 @@ export default function SingleSelectionCheckbox( {
data,
getItemId,
primaryField,
+ disabled,
} ) {
const id = getItemId( item );
const isSelected = selection.includes( id );
@@ -33,6 +34,7 @@ export default function SingleSelectionCheckbox( {
__nextHasNoMarginBottom
checked={ isSelected }
label={ selectionLabel }
+ disabled={ disabled }
onChange={ () => {
if ( ! isSelected ) {
onSelectionChange(
diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss
index 7b143958e2c16e..10e3b511cc58ae 100644
--- a/packages/dataviews/src/style.scss
+++ b/packages/dataviews/src/style.scss
@@ -13,8 +13,6 @@
.dataviews-filters__view-actions {
padding: $grid-unit-15 $grid-unit-40 0;
.components-search-control {
- flex-grow: 1;
-
.components-base-control__field {
max-width: 240px;
}
@@ -22,11 +20,7 @@
}
.dataviews-filters__container {
- padding: 0 $grid-unit-40;
-}
-
-.dataviews-filters__view-actions.components-h-stack {
- align-items: center;
+ padding-right: $grid-unit-40;
}
.dataviews-filters-button {
@@ -44,6 +38,13 @@
color: $gray-700;
}
+.dataviews-pagination__page-selection {
+ font-size: 11px;
+ text-transform: uppercase;
+ font-weight: 500;
+ color: $gray-900;
+}
+
.dataviews-filters-options {
margin: $grid-unit-40 0 $grid-unit-20;
}
@@ -118,7 +119,7 @@
background-color: #f8f8f8;
}
- .components-checkbox-control__input {
+ .components-checkbox-control__input.components-checkbox-control__input {
opacity: 0;
&:checked,
@@ -202,6 +203,12 @@
.dataviews-view-table__actions-column {
width: 1%;
}
+
+ &:has(tr.is-selected) {
+ .components-checkbox-control__input {
+ opacity: 1;
+ }
+ }
}
.dataviews-view-list__primary-field,
@@ -648,7 +655,8 @@
}
&:hover,
- &:focus-visible {
+ &:focus-visible,
+ &[aria-expanded="true"] {
background: $gray-200;
color: $gray-900;
}
@@ -657,8 +665,9 @@
color: var(--wp-admin-theme-color);
background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
- &:hover {
- background: rgba(var(--wp-admin-theme-color--rgb), 0.08);
+ &:hover,
+ &[aria-expanded="true"] {
+ background: rgba(var(--wp-admin-theme-color--rgb), 0.12);
}
}
diff --git a/packages/dataviews/src/view-grid.js b/packages/dataviews/src/view-grid.js
index 44ab1822a60750..3b46a7424dc1b1 100644
--- a/packages/dataviews/src/view-grid.js
+++ b/packages/dataviews/src/view-grid.js
@@ -22,6 +22,8 @@ import { useState } from '@wordpress/element';
import ItemActions from './item-actions';
import SingleSelectionCheckbox from './single-selection-checkbox';
+import { useHasAPossibleBulkAction } from './bulk-actions';
+
function GridItem( {
selection,
data,
@@ -34,6 +36,7 @@ function GridItem( {
visibleFields,
} ) {
const [ hasNoPointerEvents, setHasNoPointerEvents ] = useState( false );
+ const hasBulkAction = useHasAPossibleBulkAction( actions, item );
const id = getItemId( item );
const isSelected = selection.includes( id );
return (
@@ -41,11 +44,11 @@ function GridItem( {
spacing={ 0 }
key={ id }
className={ classnames( 'dataviews-view-grid__card', {
- 'is-selected': isSelected,
+ 'is-selected': hasBulkAction && isSelected,
'has-no-pointer-events': hasNoPointerEvents,
} ) }
onMouseDown={ ( event ) => {
- if ( event.ctrlKey || event.metaKey ) {
+ if ( hasBulkAction && ( event.ctrlKey || event.metaKey ) ) {
setHasNoPointerEvents( true );
if ( ! isSelected ) {
onSelectionChange(
@@ -91,6 +94,7 @@ function GridItem( {
getItemId={ getItemId }
data={ data }
primaryField={ primaryField }
+ disabled={ ! hasBulkAction }
/>
{ primaryField?.render( { item } ) }
diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js
index dc166577034257..9737aa3d462835 100644
--- a/packages/dataviews/src/view-table.js
+++ b/packages/dataviews/src/view-table.js
@@ -23,6 +23,7 @@ import {
useState,
Children,
Fragment,
+ useMemo,
} from '@wordpress/element';
/**
@@ -33,6 +34,10 @@ import { unlock } from './lock-unlock';
import ItemActions from './item-actions';
import { sanitizeOperators } from './utils';
import { ENUMERATION_TYPE, SORTING_DIRECTIONS } from './constants';
+import {
+ useSomeItemHasAPossibleBulkAction,
+ useHasAPossibleBulkAction,
+} from './bulk-actions';
const {
DropdownMenuV2: DropdownMenu,
@@ -186,8 +191,20 @@ const HeaderMenu = forwardRef( function HeaderMenu(
);
} );
-function BulkSelectionCheckbox( { selection, onSelectionChange, data } ) {
- const areAllSelected = selection.length === data.length;
+function BulkSelectionCheckbox( {
+ selection,
+ onSelectionChange,
+ data,
+ actions,
+} ) {
+ const selectableItems = useMemo( () => {
+ return data.filter( ( item ) => {
+ return actions.some(
+ ( action ) => action.supportsBulk && action.isEligible( item )
+ );
+ } );
+ }, [ data, actions ] );
+ const areAllSelected = selection.length === selectableItems.length;
return (
+ { hasBulkActions && (
+
+
+
+
+ |
+ ) }
+ { visibleFields.map( ( field ) => (
+
+
+ { field.render( {
+ item,
+ } ) }
+
+ |
+ ) ) }
+ { !! actions?.length && (
+
+
+ |
+ ) }
+
+ );
+}
+
function ViewTable( {
view,
onChangeView,
@@ -219,10 +311,10 @@ function ViewTable( {
onSelectionChange,
setOpenedFilter,
} ) {
- const hasBulkActions = actions?.some( ( action ) => action.supportsBulk );
const headerMenuRefs = useRef( new Map() );
const headerMenuToFocusRef = useRef();
const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] = useState();
+ const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
useEffect( () => {
if ( headerMenuToFocusRef.current ) {
@@ -285,6 +377,7 @@ function ViewTable( {
selection={ selection }
onSelectionChange={ onSelectionChange }
data={ data }
+ actions={ actions }
/>
) }
@@ -347,78 +440,19 @@ function ViewTable( {
{ hasData &&
usedData.map( ( item, index ) => (
-
- { hasBulkActions && (
-
-
-
-
- |
- ) }
- { visibleFields.map( ( field ) => (
-
-
- { field.render( {
- item,
- } ) }
-
- |
- ) ) }
- { !! actions?.length && (
-
-
- |
- ) }
-
+ item={ item }
+ hasBulkActions={ hasBulkActions }
+ actions={ actions }
+ id={ getItemId( item ) || index }
+ visibleFields={ visibleFields }
+ primaryField={ primaryField }
+ selection={ selection }
+ getItemId={ getItemId }
+ onSelectionChange={ onSelectionChange }
+ data={ data }
+ />
) ) }
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php
index 428d47ec397957..6b20b2dba8376e 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/render.php
@@ -18,6 +18,7 @@
>
+
+
@@ -59,6 +68,7 @@
>
+
@@ -143,3 +158,41 @@
+
+
+
Select 1
+
Select 2
+
+
+
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js
index 7238185bac3aa1..9437edced74a44 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-context/view.js
@@ -9,6 +9,10 @@ store( 'directive-context', {
const ctx = getContext();
return JSON.stringify( ctx, undefined, 2 );
},
+ get selected() {
+ const { list, selected } = getContext();
+ return list.find( ( obj ) => obj === selected )?.text;
+ }
},
actions: {
updateContext( event ) {
@@ -22,6 +26,15 @@ store( 'directive-context', {
const ctx = getContext();
ctx.text = ctx.text === 'Text 1' ? 'Text 2' : 'Text 1';
},
+ selectItem( event ) {
+ const ctx = getContext();
+ const value = parseInt( event.target.value );
+ ctx.selected = ctx.list.find( ( { id } ) => id === value );
+ },
+ replaceObj() {
+ const ctx = getContext();
+ ctx.obj = { overwritten: true };
+ }
},
} );
@@ -29,12 +42,17 @@ const html = `
+
Toggle Text
-
Add new text
+
Add New Text
+
Add Text 2
Navigate
Async Navigate
`;
@@ -49,13 +67,17 @@ const { actions } = store( 'directive-context-navigate', {
const ctx = getContext();
ctx.newText = 'some new text';
},
+ addText2() {
+ const ctx = getContext();
+ ctx.text2 = 'some new text';
+ },
navigate() {
return import( '@wordpress/interactivity-router' ).then(
- ( { actions: routerActions } ) =>
- routerActions.navigate(
- window.location,
- { force: true, html },
- )
+ ( { actions: routerActions } ) => {
+ const url = new URL( window.location.href );
+ url.searchParams.set( 'next_page', 'true' );
+ return routerActions.navigate( url, { force: true, html } );
+ }
);
},
@@ -66,3 +88,21 @@ const { actions } = store( 'directive-context-navigate', {
},
},
} );
+
+store( 'directive-context-watch', {
+ actions: {
+ increment: () => {
+ const ctx = getContext();
+ ctx.counter = ctx.counter + 1;
+ },
+ },
+ callbacks: {
+ countChanges: () => {
+ const ctx = getContext();
+ // Subscribe to changes in counter.
+ // eslint-disable-next-line no-unused-expressions
+ ctx.counter;
+ ctx.changes = ctx.changes + 1;
+ },
+ },
+});
diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php
index 0b8e6e1012d1a4..d1a7aa9211f105 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php
+++ b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php
@@ -15,6 +15,13 @@
array( 'clientNavigationDisabled' => true )
);
}
+
+if ( isset( $attributes['data'] ) ) {
+ wp_interactivity_state(
+ 'router',
+ array( 'data' => $attributes['data'] )
+ );
+}
?>
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js
index 1e137969936a09..b2d4ad0dc1ddeb 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js
+++ b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js
@@ -6,14 +6,23 @@ import { store } from '@wordpress/interactivity';
const { state } = store( 'router', {
state: {
status: 'idle',
- navigations: 0,
+ navigations: {
+ pending: 0,
+ count: 0,
+ },
timeout: 10000,
+ data: {
+ get getterProp() {
+ return `value from getter (${ state.data.prop1 })`;
+ }
+ }
},
actions: {
*navigate( e ) {
e.preventDefault();
- state.navigations += 1;
+ state.navigations.count += 1;
+ state.navigations.pending += 1;
state.status = 'busy';
const force = e.target.dataset.forceNavigation === 'true';
@@ -24,9 +33,9 @@ const { state } = store( 'router', {
);
yield actions.navigate( e.target.href, { force, timeout } );
- state.navigations -= 1;
+ state.navigations.pending -= 1;
- if ( state.navigations === 0 ) {
+ if ( state.navigations.pending === 0 ) {
state.status = 'idle';
}
},
diff --git a/packages/e2e-tests/plugins/interactive-blocks/store/block.json b/packages/e2e-tests/plugins/interactive-blocks/store/block.json
new file mode 100644
index 00000000000000..cddd765ba3db53
--- /dev/null
+++ b/packages/e2e-tests/plugins/interactive-blocks/store/block.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "test/store",
+ "title": "E2E Interactivity tests - store definition",
+ "category": "text",
+ "icon": "heart",
+ "description": "",
+ "supports": {
+ "interactivity": true
+ },
+ "textdomain": "e2e-interactivity",
+ "viewScript": "store-view",
+ "render": "file:./render.php"
+}
diff --git a/packages/e2e-tests/plugins/interactive-blocks/store/render.php b/packages/e2e-tests/plugins/interactive-blocks/store/render.php
new file mode 100644
index 00000000000000..295872137dbdae
--- /dev/null
+++ b/packages/e2e-tests/plugins/interactive-blocks/store/render.php
@@ -0,0 +1,17 @@
+
+
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/store/view.js b/packages/e2e-tests/plugins/interactive-blocks/store/view.js
new file mode 100644
index 00000000000000..b4a987b5e85b5e
--- /dev/null
+++ b/packages/e2e-tests/plugins/interactive-blocks/store/view.js
@@ -0,0 +1,20 @@
+/**
+ * WordPress dependencies
+ */
+import { store, getElement } from '@wordpress/interactivity';
+
+
+const { state } = store( 'test/store', {
+ state: {
+ get isNotProxified() {
+ const { ref } = getElement();
+ return state.elementRef === ref;
+ }
+ },
+ callbacks: {
+ init() {
+ const { ref } = getElement();
+ state.elementRef = ref; // HTMLElement
+ }
+ }
+} )
diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js
index b573ac43fe5fa7..b1aba274a155c0 100644
--- a/packages/edit-post/src/components/header/index.js
+++ b/packages/edit-post/src/components/header/index.js
@@ -61,7 +61,7 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) {
const blockToolbarRef = useRef();
const {
isTextEditor,
- hasBlockSelection,
+ blockSelectionStart,
hasActiveMetaboxes,
hasFixedToolbar,
isPublishSidebarOpened,
@@ -73,8 +73,8 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) {
return {
isTextEditor: getEditorMode() === 'text',
- hasBlockSelection:
- !! select( blockEditorStore ).getBlockSelectionStart(),
+ blockSelectionStart:
+ select( blockEditorStore ).getBlockSelectionStart(),
hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(),
hasHistory:
!! select( editorStore ).getEditorSettings()
@@ -88,13 +88,14 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) {
const [ isBlockToolsCollapsed, setIsBlockToolsCollapsed ] =
useState( true );
+ const hasBlockSelection = !! blockSelectionStart;
useEffect( () => {
// If we have a new block selection, show the block tools
- if ( hasBlockSelection ) {
+ if ( blockSelectionStart ) {
setIsBlockToolsCollapsed( false );
}
- }, [ hasBlockSelection ] );
+ }, [ blockSelectionStart ] );
return (
@@ -121,7 +122,9 @@ function Header( { setEntitiesSavedStatesCallback, initialPost } ) {
className={ classnames(
'selected-block-tools-wrapper',
{
- 'is-collapsed': isBlockToolsCollapsed,
+ 'is-collapsed':
+ isBlockToolsCollapsed ||
+ ! hasBlockSelection,
}
) }
>
diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js
index f5b4a27e158ea0..98f67e7e4ee5c6 100644
--- a/packages/edit-post/src/store/selectors.js
+++ b/packages/edit-post/src/store/selectors.js
@@ -556,7 +556,7 @@ export const isEditingTemplate = createRegistrySelector( ( select ) => () => {
since: '6.5',
alternative: `select( 'core/editor' ).getRenderingMode`,
} );
- return select( editorStore ).getCurrentPostType() !== 'post-only';
+ return select( editorStore ).getCurrentPostType() === 'wp_template';
} );
/**
diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js b/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js
deleted file mode 100644
index a6962952661939..00000000000000
--- a/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-details.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- __experimentalVStack as VStack,
- __experimentalSpacer as Spacer,
-} from '@wordpress/components';
-
-/**
- * Internal dependencies
- */
-import CollectionFontVariant from './collection-font-variant';
-import { isFontFontFaceInOutline } from './utils/fonts-outline';
-import { sortFontFaces } from './utils/sort-font-faces';
-
-function CollectionFontDetails( {
- font,
- handleToggleVariant,
- fontToInstallOutline,
-} ) {
- const fontFaces =
- font.fontFace && font.fontFace.length
- ? sortFontFaces( font.fontFace )
- : [
- {
- fontFamily: font.fontFamily,
- fontStyle: 'normal',
- fontWeight: '400',
- },
- ];
-
- return (
- <>
-
-
-
- { fontFaces.map( ( face, i ) => (
-
- ) ) }
-
-
- >
- );
-}
-
-export default CollectionFontDetails;
diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-variant.js b/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-variant.js
index 0db8932a98e34d..04300be95a0cff 100644
--- a/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-variant.js
+++ b/packages/edit-site/src/components/global-styles/font-library-modal/collection-font-variant.js
@@ -11,7 +11,7 @@ import {
* Internal dependencies
*/
import { getFontFaceVariantName } from './utils';
-import FontFaceDemo from './font-demo';
+import FontDemo from './font-demo';
import { unlock } from '../../../lock-unlock';
function CollectionFontVariant( {
@@ -35,7 +35,7 @@ function CollectionFontVariant( {
);
return (
-
+