diff --git a/backport-changelog/6.7/6668.md b/backport-changelog/6.7/6668.md new file mode 100644 index 0000000000000..7653dd8d8294e --- /dev/null +++ b/backport-changelog/6.7/6668.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6668 + +* https://github.com/WordPress/gutenberg/pull/62092 diff --git a/backport-changelog/6.7/6910.md b/backport-changelog/6.7/6910.md index 8e6be0dc8e7a5..962f54be672ad 100644 --- a/backport-changelog/6.7/6910.md +++ b/backport-changelog/6.7/6910.md @@ -2,4 +2,5 @@ https://github.com/WordPress/wordpress-develop/pull/6910 * https://github.com/WordPress/gutenberg/pull/59483 * https://github.com/WordPress/gutenberg/pull/60652 -* https://github.com/WordPress/gutenberg/pull/62777 \ No newline at end of file +* https://github.com/WordPress/gutenberg/pull/62777 +* https://github.com/WordPress/gutenberg/pull/63108 \ No newline at end of file diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index 2d4a93a080654..d79f1cdb6c56b 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -427,7 +427,7 @@ See the [Example documentation](/docs/reference-guides/block-api/block-registrat ### Variations -- Type: `object[]` +- Type: `object[]|WPDefinedPath` ([learn more](#wpdefinedpath)) - Optional - Localized: Yes (`title`, `description`, and `keywords` of each variation only) - Property: `variations` @@ -454,6 +454,48 @@ Block Variations is the API that allows a block to have similar versions of it, _Note: In JavaScript you can provide a function for the `isActive` property, and a React element for the `icon`. In the `block.json` file both only support strings_ +Starting with version 6.7, it is possible to specify a PHP file in `block.json` that generates the list of block variations on the server side: + +```json +{ "variations": "file:./variations.php" } +``` + +That PHP file is expected to `return` an array that contains the block variations. Strings found in the variations returned from the PHP file will not be localized automatically; instead, use the `__()` function as usual. + +For example: + +```php + true, + 'name' => 'wordpress', + 'title' => 'WordPress', + 'icon' => 'wordpress', + 'attributes' => array( + 'service' => 'wordpress', + ), + 'isActive' => array( 'service' ) + ), + array( + 'name' => 'mail', + 'title' => __( 'Mail' ), + 'keywords' => array( + __( 'email' ), + __( 'e-mail' ) + ), + 'icon' => 'mail', + 'attributes' => array( + 'service' => 'mail', + ), + 'isActive' => array( 'mail' ) + ), +); + +``` + See [the variations documentation](/docs/reference-guides/block-api/block-variations.md) for more details. ### Block Hooks diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index d81a46cd727a1..4bfe230f6df03 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -24,7 +24,7 @@ Embed a simple audio player. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/audio - **Category:** media - **Supports:** align, anchor, interactivity (clientNavigation), spacing (margin, padding) -- **Attributes:** autoplay, caption, id, loop, preload, src +- **Attributes:** autoplay, blob, caption, id, loop, preload, src ## Avatar @@ -273,7 +273,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb - **Name:** core/file - **Category:** media - **Supports:** align, anchor, color (background, gradients, link, ~~text~~), interactivity, spacing (margin, padding) -- **Attributes:** displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget +- **Attributes:** blob, displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget ## Footnotes diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index b5e2fe37faecd..a8cc85dd304e7 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -501,7 +501,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support if ( ! empty( $layout['rowCount'] ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(8px, auto))' ), + 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ), ); } } elseif ( ! empty( $layout['columnCount'] ) ) { @@ -512,7 +512,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support if ( ! empty( $layout['rowCount'] ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(8px, auto))' ), + 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ), ); } } else { diff --git a/lib/compat/wordpress-6.7/blocks.php b/lib/compat/wordpress-6.7/blocks.php new file mode 100644 index 0000000000000..18d21621be719 --- /dev/null +++ b/lib/compat/wordpress-6.7/blocks.php @@ -0,0 +1,45 @@ + { + ( { gridClientId, gridElement, isManualGrid }, ref ) => { const [ gridInfo, setGridInfo ] = useState( () => getGridInfo( gridElement ) ); const [ isDroppingAllowed, setIsDroppingAllowed ] = useState( false ); - const [ highlightedRect, setHighlightedRect ] = useState( null ); useEffect( () => { const observers = []; @@ -88,7 +88,7 @@ const GridVisualizerGrid = forwardRef( className={ clsx( 'block-editor-grid-visualizer', { 'is-dropping-allowed': isDroppingAllowed, } ) } - clientId={ clientId } + clientId={ gridClientId } __unstablePopoverSlot="block-toolbar" >
- { isManualGrid - ? range( 1, gridInfo.numRows ).map( ( row ) => - range( 1, gridInfo.numColumns ).map( - ( column ) => ( - - - - ) - ) - ) - : Array.from( - { length: gridInfo.numItems }, - ( _, i ) => ( - - ) - ) } + { isManualGrid ? ( + + ) : ( + Array.from( { length: gridInfo.numItems }, ( _, i ) => ( + + ) ) + ) }
); } ); -function GridVisualizerCell( { color, children } ) { +function ManualGridVisualizer( { gridClientId, gridInfo } ) { + const [ highlightedRect, setHighlightedRect ] = useState( null ); + + const gridItems = useSelect( + ( select ) => select( blockEditorStore ).getBlocks( gridClientId ), + [ gridClientId ] + ); + const occupiedRects = useMemo( () => { + const rects = []; + for ( const block of gridItems ) { + const { + columnStart, + rowStart, + columnSpan = 1, + rowSpan = 1, + } = block.attributes.style?.layout || {}; + if ( ! columnStart || ! rowStart ) { + continue; + } + rects.push( + new GridRect( { + columnStart, + rowStart, + columnSpan, + rowSpan, + } ) + ); + } + return rects; + }, [ gridItems ] ); + + return range( 1, gridInfo.numRows ).map( ( row ) => + range( 1, gridInfo.numColumns ).map( ( column ) => { + const isCellOccupied = occupiedRects.some( ( rect ) => + rect.contains( column, row ) + ); + const isHighlighted = + highlightedRect?.contains( column, row ) ?? false; + return ( + + { isCellOccupied ? ( + + ) : ( + + ) } + + ); + } ) + ); +} + +function GridVisualizerCell( { color, children, className } ) { return (
{ children } @@ -148,15 +199,15 @@ function GridVisualizerCell( { color, children } ) { ); } -function GridVisualizerDropZone( { +function useGridVisualizerDropZone( column, row, gridClientId, gridInfo, - highlightedRect, - setHighlightedRect, -} ) { - const { getBlockAttributes } = useSelect( blockEditorStore ); + setHighlightedRect +) { + const { getBlockAttributes, getBlockRootClientId } = + useSelect( blockEditorStore ); const { updateBlockAttributes, moveBlocksToPosition, @@ -168,7 +219,7 @@ function GridVisualizerDropZone( { gridInfo.numColumns ); - const ref = useDropZoneWithValidation( { + return useDropZoneWithValidation( { validateDrag( srcClientId ) { const attributes = getBlockAttributes( srcClientId ); const rect = new GridRect( { @@ -221,21 +272,87 @@ function GridVisualizerDropZone( { __unstableMarkNextChangeAsNotPersistent(); moveBlocksToPosition( [ srcClientId ], - gridClientId, + getBlockRootClientId( srcClientId ), gridClientId, getNumberOfBlocksBeforeCell( column, row ) ); }, } ); +} - const isHighlighted = highlightedRect?.contains( column, row ) ?? false; - +function GridVisualizerDropZone( { + column, + row, + gridClientId, + gridInfo, + setHighlightedRect, +} ) { return (
+ ); +} + +function GridVisualizerAppender( { + column, + row, + gridClientId, + gridInfo, + setHighlightedRect, +} ) { + const { + updateBlockAttributes, + moveBlocksToPosition, + __unstableMarkNextChangeAsNotPersistent, + } = useDispatch( blockEditorStore ); + + const getNumberOfBlocksBeforeCell = useGetNumberOfBlocksBeforeCell( + gridClientId, + gridInfo.numColumns + ); + + return ( + { + if ( ! block ) { + return; + } + updateBlockAttributes( block.clientId, { + style: { + layout: { + columnStart: column, + rowStart: row, + }, + }, + } ); + __unstableMarkNextChangeAsNotPersistent(); + moveBlocksToPosition( + [ block.clientId ], + gridClientId, + gridClientId, + getNumberOfBlocksBeforeCell( column, row ) + ); + } } /> ); } diff --git a/packages/block-editor/src/components/grid/style.scss b/packages/block-editor/src/components/grid/style.scss index 17513fa6496b2..a4d2c58895932 100644 --- a/packages/block-editor/src/components/grid/style.scss +++ b/packages/block-editor/src/components/grid/style.scss @@ -12,6 +12,9 @@ pointer-events: all; } } + .block-editor-inserter * { + pointer-events: auto; + } } } @@ -20,23 +23,55 @@ } .block-editor-grid-visualizer__cell { - align-items: center; - display: flex; - justify-content: center; + display: grid; + position: relative; + + .block-editor-inserter { + color: inherit; + z-index: 32; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: hidden; + + .block-editor-grid-visualizer__appender { + box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 20%, #0000); + color: inherit; + overflow: hidden; + height: 100%; + width: 100%; + padding: 0 !important; + opacity: 0; + } + + } + + &.is-highlighted { + .block-editor-inserter, + .block-editor-grid-visualizer__drop-zone { + background: var(--wp-admin-theme-color); + } + } + + &:hover .block-editor-grid-visualizer__appender, + .block-editor-grid-visualizer__appender:focus { + opacity: 1; + background-color: color-mix(in srgb, currentColor 20%, #0000); + } } .block-editor-grid-visualizer__drop-zone { background: rgba($gray-400, 0.1); width: 100%; height: 100%; + grid-column: 1; + grid-row: 1; // Make drop zone 8x8 at minimum so that it's easier to drag into. This will overflow the parent. min-width: $grid-unit-10; min-height: $grid-unit-10; - - &.is-highlighted { - background: var(--wp-admin-theme-color); - } } .block-editor-grid-item-resizer { @@ -60,3 +95,4 @@ } } } + diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index bcd60a687a36f..ef741a9b6ac61 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -88,7 +88,7 @@ function InserterMenu( shouldForceFocusBlock, _rootClientId ); - onSelect(); + onSelect( blocks ); // Check for focus loss due to filtering blocks by selected block type window.requestAnimationFrame( () => { diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index 022957df952ce..aa3d54e87a7fd 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -85,7 +85,12 @@ export default function QuickInserter( { // When clicking Browse All select the appropriate block so as // the insertion point can work as expected. const onBrowseAll = () => { - setInserterIsOpened( { rootClientId, insertionIndex, filterValue } ); + setInserterIsOpened( { + rootClientId, + insertionIndex, + filterValue, + onSelect, + } ); }; let maxBlockPatterns = 0; diff --git a/packages/block-editor/src/hooks/grid-visualizer.js b/packages/block-editor/src/hooks/grid-visualizer.js new file mode 100644 index 0000000000000..42b45952d45d1 --- /dev/null +++ b/packages/block-editor/src/hooks/grid-visualizer.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { createHigherOrderComponent } from '@wordpress/compose'; +import { addFilter } from '@wordpress/hooks'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { GridVisualizer, useGridLayoutSync } from '../components/grid'; +import { store as blockEditorStore } from '../store'; + +function GridLayoutSync( props ) { + useGridLayoutSync( props ); +} + +function GridTools( { clientId, layout } ) { + const { isSelected, isDragging } = useSelect( ( select ) => { + const { isBlockSelected, isDraggingBlocks } = + select( blockEditorStore ); + + return { + isSelected: isBlockSelected( clientId ), + isDragging: isDraggingBlocks(), + }; + } ); + + if ( ! isSelected && ! isDragging ) { + return null; + } + + return ( + <> + + + + ); +} + +const addGridVisualizerToBlockEdit = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + if ( props.attributes.layout?.type !== 'grid' ) { + return ; + } + + return ( + <> + + + + ); + }, + 'addGridVisualizerToBlockEdit' +); + +addFilter( + 'editor.BlockEdit', + 'core/editor/grid-visualizer', + addGridVisualizerToBlockEdit +); diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 423e1f6f0bc32..b58ede2e9d389 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -33,6 +33,7 @@ import blockHooks from './block-hooks'; import blockBindingsPanel from './block-bindings'; import './block-renaming'; import './use-bindings-attributes'; +import './grid-visualizer'; createBlockEditFilter( [ diff --git a/packages/block-editor/src/layouts/constrained.js b/packages/block-editor/src/layouts/constrained.js index f86791391176f..03d2c642d02bd 100644 --- a/packages/block-editor/src/layouts/constrained.js +++ b/packages/block-editor/src/layouts/constrained.js @@ -24,6 +24,7 @@ import { getCSSRules } from '@wordpress/style-engine'; import { useSettings } from '../components/use-settings'; import { appendSelectors, getBlockGapCSS, getAlignmentsInfo } from './utils'; import { getGapCSSValue } from '../hooks/gap'; +import { BlockControls, JustifyContentControl } from '../components'; import { shouldSkipSerialization } from '../hooks/utils'; import { LAYOUT_DEFINITIONS } from './definitions'; @@ -146,8 +147,24 @@ export default { ); }, - toolBarControls: function DefaultLayoutToolbarControls() { - return null; + toolBarControls: function DefaultLayoutToolbarControls( { + layout = {}, + onChange, + layoutBlockSupport, + } ) { + const { allowJustification = true } = layoutBlockSupport; + + if ( ! allowJustification ) { + return null; + } + return ( + + + + ); }, getLayoutStyle: function getLayoutStyle( { selector, @@ -278,3 +295,27 @@ export default { return alignments; }, }; + +const POPOVER_PROPS = { + placement: 'bottom-start', +}; + +function DefaultLayoutJustifyContentControl( { layout, onChange } ) { + const { justifyContent = 'center' } = layout; + const onJustificationChange = ( value ) => { + onChange( { + ...layout, + justifyContent: value, + } ); + }; + const allowedControls = [ 'left', 'center', 'right' ]; + + return ( + + ); +} diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 21870fc182093..975b79f4ba570 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -23,7 +23,6 @@ import { appendSelectors, getBlockGapCSS } from './utils'; import { getGapCSSValue } from '../hooks/gap'; import { shouldSkipSerialization } from '../hooks/utils'; import { LAYOUT_DEFINITIONS } from './definitions'; -import { GridVisualizer, useGridLayoutSync } from '../components/grid'; const RANGE_CONTROL_MAX_VALUES = { px: 600, @@ -101,15 +100,8 @@ export default { ); }, - toolBarControls: function GridLayoutToolbarControls( { clientId } ) { - return ( - <> - { window.__experimentalEnableGridInteractivity && ( - - ) } - - - ); + toolBarControls: function GridLayoutToolbarControls() { + return null; }, getLayoutStyle: function getLayoutStyle( { selector, @@ -163,7 +155,7 @@ export default { ); if ( rowCount ) { rules.push( - `grid-template-rows: repeat(${ rowCount }, minmax(8px, auto))` + `grid-template-rows: repeat(${ rowCount }, minmax(1rem, auto))` ); } } else if ( columnCount ) { @@ -172,7 +164,7 @@ export default { ); if ( rowCount ) { rules.push( - `grid-template-rows: repeat(${ rowCount }, minmax(8px, auto))` + `grid-template-rows: repeat(${ rowCount }, minmax(1rem, auto))` ); } } else { @@ -479,7 +471,3 @@ function GridLayoutTypeControl( { layout, onChange } ) { ); } - -function GridLayoutSync( props ) { - useGridLayoutSync( props ); -} diff --git a/packages/block-library/src/audio/block.json b/packages/block-library/src/audio/block.json index 14b44704fb7e8..bee2ff6d534a7 100644 --- a/packages/block-library/src/audio/block.json +++ b/packages/block-library/src/audio/block.json @@ -8,6 +8,10 @@ "keywords": [ "music", "sound", "podcast", "recording" ], "textdomain": "default", "attributes": { + "blob": { + "type": "string", + "__experimentalRole": "local" + }, "src": { "type": "string", "source": "attribute", diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 0db2552b8ac26..97d3c9c5a1ae3 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -26,6 +26,7 @@ import { __, _x } from '@wordpress/i18n'; import { useDispatch } from '@wordpress/data'; import { audio as icon } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -45,10 +46,10 @@ function AudioEdit( { insertBlocksAfter, } ) { const { id, autoplay, loop, preload, src } = attributes; - const isTemporaryAudio = ! id && isBlobURL( src ); + const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); useUploadMediaFromBlobURL( { - url: src, + url: temporaryURL, allowedTypes: ALLOWED_MEDIA_TYPES, onChange: onSelectAudio, onError: onUploadError, @@ -72,7 +73,8 @@ function AudioEdit( { onReplace( embedBlock ); return; } - setAttributes( { src: newSrc, id: undefined } ); + setAttributes( { src: newSrc, id: undefined, blob: undefined } ); + setTemporaryURL(); } } @@ -95,27 +97,37 @@ function AudioEdit( { src: undefined, id: undefined, caption: undefined, + blob: undefined, } ); + setTemporaryURL(); return; } + + if ( isBlobURL( media.url ) ) { + setTemporaryURL( media.url ); + return; + } + // Sets the block's attribute and updates the edit component from the // selected media, then switches off the editing UI. setAttributes( { + blob: undefined, src: media.url, id: media.id, caption: media.caption, } ); + setTemporaryURL(); } const classes = clsx( className, { - 'is-transient': isTemporaryAudio, + 'is-transient': !! temporaryURL, } ); const blockProps = useBlockProps( { className: classes, } ); - if ( ! src ) { + if ( ! src && ! temporaryURL ) { return (
-