From 89e3ad3a51a9991486bf12617ec3984ae812431a Mon Sep 17 00:00:00 2001 From: epiqueras Date: Sun, 2 Feb 2020 16:45:55 -0500 Subject: [PATCH] Block Editor: Implement an experimental `InnerBlocks` grid mode. --- package-lock.json | 40 ++- packages/block-editor/package.json | 4 +- .../src/components/block-list/block.js | 4 +- .../src/components/block-list/grid-utils.js | 167 +++++++++++++ .../src/components/block-list/grid.js | 231 ++++++++++++++++++ .../src/components/block-list/index.js | 96 +++++++- .../src/components/block-list/style.scss | 35 +++ .../src/components/inner-blocks/index.js | 11 +- packages/block-library/src/grid/block.json | 23 ++ packages/block-library/src/grid/edit.js | 8 + packages/block-library/src/grid/index.js | 20 ++ packages/block-library/src/grid/save.js | 8 + packages/block-library/src/index.js | 2 + packages/blocks/src/api/serializer.js | 2 +- .../src/block-content-provider/index.js | 4 +- packages/viewport/README.md | 12 + packages/viewport/src/index.js | 2 +- 17 files changed, 651 insertions(+), 18 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/grid-utils.js create mode 100644 packages/block-editor/src/components/block-list/grid.js create mode 100644 packages/block-library/src/grid/block.json create mode 100644 packages/block-library/src/grid/edit.js create mode 100644 packages/block-library/src/grid/index.js create mode 100644 packages/block-library/src/grid/save.js diff --git a/package-lock.json b/package-lock.json index 7082f93ae92e6f..df93443f62616f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10093,12 +10093,21 @@ "lodash": "^4.17.15", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", + "react-grid-layout": "^0.17.1", "react-spring": "^8.0.19", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", - "traverse": "^0.6.6" + "traverse": "^0.6.6", + "uuid": "^3.3.3" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } } }, "@wordpress/block-library": { @@ -19703,7 +19712,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -19722,7 +19731,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -26565,8 +26574,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.ismatch": { "version": "4.4.0", @@ -33209,7 +33217,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.2.0.tgz", "integrity": "sha512-5wFq//gEoeTYprnd4ze8GrFc+Rbnx+9RkOMR3vk4EbWxj02U6L6T3yrlKeiw4X5CtjD2ma2+b3WujghcXNRzkw==", - "dev": true, "requires": { "classnames": "^2.2.5", "prop-types": "^15.6.0" @@ -33268,6 +33275,18 @@ "use-sidecar": "^1.0.1" } }, + "react-grid-layout": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.17.1.tgz", + "integrity": "sha512-L+wHFevK+klKvoAHuHn4Q5qHtrW+4zCj0F3QnpR7wZbkZPmrdaWC7cztFLXwINq6WnOWGE22BCTCDCHJi7dVDw==", + "requires": { + "classnames": "2.x", + "lodash.isequal": "^4.0.0", + "prop-types": "^15.0.0", + "react-draggable": "^4.0.0", + "react-resizable": "^1.9.0" + } + }, "react-helmet-async": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.0.4.tgz", @@ -33873,6 +33892,15 @@ "integrity": "sha512-ITw8t/HOFNose2yf1y9pPFSSeB9ISOq2JdHpuZvj/Qb+iSsLml8GkkHdDlURzieO7B3dFDtMrrneZLl3N5z/hg==", "dev": true }, + "react-resizable": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.10.1.tgz", + "integrity": "sha512-Jd/bKOKx6+19NwC4/aMLRu/J9/krfxlDnElP41Oc+oLiUWs/zwV1S9yBfBZRnqAwQb6vQ/HRSk3bsSWGSgVbpw==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + }, "react-resize-aware": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-resize-aware/-/react-resize-aware-3.0.0.tgz", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 9f8f952fbbb150..400d401dd18f46 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -52,12 +52,14 @@ "lodash": "^4.17.15", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", + "react-grid-layout": "^0.17.1", "react-spring": "^8.0.19", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", - "traverse": "^0.6.6" + "traverse": "^0.6.6", + "uuid": "^3.3.3" }, "publishConfig": { "access": "public" diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 3fb84e824f1082..3055671c2f0025 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -337,7 +337,7 @@ function BlockListBlock( { } const applyWithSelect = withSelect( - ( select, { clientId, rootClientId, isLargeViewport } ) => { + ( select, { clientId, rootClientId, isLargeViewport, isLocked } ) => { const { isBlockSelected, isAncestorMultiSelected, @@ -393,7 +393,7 @@ const applyWithSelect = withSelect( : null, isEmptyDefaultBlock: name && isUnmodifiedDefaultBlock( { name, attributes } ), - isLocked: !! templateLock, + isLocked: isLocked || !! templateLock, isFocusMode: focusMode && isLargeViewport, isNavigationMode: isNavigationMode(), isRTL, diff --git a/packages/block-editor/src/components/block-list/grid-utils.js b/packages/block-editor/src/components/block-list/grid-utils.js new file mode 100644 index 00000000000000..c7417f6675f339 --- /dev/null +++ b/packages/block-editor/src/components/block-list/grid-utils.js @@ -0,0 +1,167 @@ +/** + * External dependencies + */ +import uuid from 'uuid/v4'; + +export function createInitialLayouts( grid, blockClientIds ) { + // Hydrate grid layout, if any, with new block client IDs. + return grid ? + grid.map( ( item, i ) => ( { + ...item, + i: `block-${ blockClientIds[ i ] }`, + } ) ) : + []; +} + +export function appendNewBlocks( + nextLayout, + lastClickedBlockAppenderId, + prevBlockClientIds, + blockClientIds +) { + if ( + blockClientIds.length && + ! prevBlockClientIds.includes( blockClientIds[ blockClientIds.length - 1 ] ) + ) { + // If a block client ID has been added, make its block's position and dimensions + // that of the last clicked block appender, since it must be the one that added it. + const appenderItem = nextLayout.find( + ( item ) => item.i === lastClickedBlockAppenderId + ); + nextLayout = nextLayout + .map( ( item ) => { + switch ( item.i ) { + case lastClickedBlockAppenderId: + return { + ...appenderItem, + i: `block-${ blockClientIds[ blockClientIds.length - 1 ] }`, + }; + case blockClientIds[ blockClientIds.length - 1 ]: + return null; + default: + return item; + } + } ) + .filter( Boolean ); + } + + return nextLayout; +} + +export function resizeOverflowingBlocks( nextLayout, nodes ) { + const cellChanges = {}; + const itemsMap = nextLayout.reduce( ( acc, item ) => { + acc[ item.i ] = item; + return acc; + }, {} ); + + for ( const node of Object.values( nodes ) ) { + if ( ! itemsMap[ node.id ] ) { + continue; + } + const { clientWidth, clientHeight } = node.parentNode; + const minCols = Math.ceil( + node.offsetWidth / ( clientWidth / itemsMap[ node.id ].w ) + ); + const minRows = Math.ceil( + ( node.offsetHeight - 20 ) / ( clientHeight / itemsMap[ node.id ].h ) + ); + if ( itemsMap[ node.id ].w < minCols || itemsMap[ node.id ].h < minRows ) { + cellChanges[ node.id ] = { + w: Math.max( itemsMap[ node.id ].w, minCols ), + h: Math.max( itemsMap[ node.id ].h, minRows ), + }; + } + } + if ( Object.keys( cellChanges ).length ) { + nextLayout = nextLayout.map( ( item ) => + cellChanges[ item.i ] ? { ...item, ...cellChanges[ item.i ] } : item + ); + } + + return nextLayout; +} + +export function cropAndFillEmptyCells( nextLayout, cols, rows ) { + const maxRow = + Math.max( + rows, + ...nextLayout + .filter( ( item ) => ! item.i.startsWith( 'block-appender' ) ) + .map( ( item ) => item.y + item.h ) + ) - 1; + if ( nextLayout.some( ( item ) => item.y > maxRow ) ) { + // Crop extra rows. + nextLayout = nextLayout.filter( ( item ) => item.y <= maxRow ); + } + + const emptyCells = {}; + for ( + let col = 0; + col <= Math.max( cols, ...nextLayout.map( ( item ) => item.x + item.w ) ) - 1; + col++ + ) { + for ( let row = 0; row <= maxRow; row++ ) { + emptyCells[ `${ col } | ${ row }` ] = true; + } + } + for ( const item of nextLayout ) { + for ( let col = item.x; col < item.x + item.w; col++ ) { + for ( let row = item.y; row < item.y + item.h; row++ ) { + delete emptyCells[ `${ col } | ${ row }` ]; + } + } + } + if ( Object.keys( emptyCells ).length ) { + // Fill empty cells with block appenders. + nextLayout = [ + ...nextLayout, + ...Object.keys( emptyCells ).map( ( emptyCell ) => { + const [ col, row ] = emptyCell.split( ' | ' ); + return { + i: `block-appender-${ uuid() }`, + x: Number( col ), + y: Number( row ), + w: 1, + h: 1, + }; + } ), + ]; + } + + return nextLayout; +} + +export function hashGrid( grid ) { + return Object.values( JSON.stringify( grid ) ).reduce( ( acc, char ) => { + /* eslint-disable no-bitwise */ + acc = ( acc << 5 ) - acc + char.charCodeAt( 0 ); + return acc & acc; + /* eslint-enable no-bitwise */ + } ); +} + +function createGridItemsStyleRules( gridId, items ) { + return items + .map( + ( item, i ) => `#${ gridId } > #editor-block-list__grid-content-item-${ i } { + grid-area: ${ item.y + 1 } / ${ item.x + 1 } / ${ item.y + 1 + item.h } / ${ item.x + + 1 + + item.w } + }` + ) + .join( '\n\n ' ); +} +export function createGridStyleRules( gridId, grid, breakpoint, cols, rows ) { + const maxCol = Math.max( cols, ...grid.map( ( item ) => item.x + item.w ) ) - 1; + const maxRow = Math.max( rows, ...grid.map( ( item ) => item.y + item.h ) ) - 1; + return `@media (min-width: ${ breakpoint }px) { + #${ gridId } { + grid-template-columns: repeat(${ maxCol + 1 }, 1fr); + grid-template-rows: repeat(${ maxRow + 1 }, 1fr); + + } + + ${ createGridItemsStyleRules( gridId, grid ) } + }`; +} diff --git a/packages/block-editor/src/components/block-list/grid.js b/packages/block-editor/src/components/block-list/grid.js new file mode 100644 index 00000000000000..60ebbd4a02c66a --- /dev/null +++ b/packages/block-editor/src/components/block-list/grid.js @@ -0,0 +1,231 @@ +/** + * External dependencies + */ +import _ReactGridLayout, { WidthProvider } from 'react-grid-layout'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useSelect, useDispatch, AsyncModeProvider } from '@wordpress/data'; +import { + useState, + useRef, + useEffect, + RawHTML, + useContext, +} from '@wordpress/element'; +import { serialize } from '@wordpress/blocks'; +import { BREAKPOINTS } from '@wordpress/viewport'; + +/** + * Internal dependencies + */ +import BlockListAppender from '../block-list-appender'; +import ButtonBlockAppender from '../inner-blocks/button-block-appender'; +import BlockListBlock from './block'; +import { + createInitialLayouts, + appendNewBlocks, + resizeOverflowingBlocks, + cropAndFillEmptyCells, + hashGrid, + createGridStyleRules, +} from './grid-utils'; +import { BlockNodes } from './root-container'; + +const ReactGridLayout = WidthProvider( _ReactGridLayout ); +function BlockGrid( { + rootClientId, + blockClientIds, + className, + hasMultiSelection, + multiSelectedBlockClientIds, + selectedBlockClientId, + isDraggable, + isMultiSelecting, + enableAnimation, + __experimentalUIParts, + targetClientId, +} ) { + const [ nodes ] = useContext( BlockNodes ); + const { grid, cols = 2, rows = 2 } = useSelect( + ( select ) => + select( 'core/block-editor' ).getBlockAttributes( rootClientId ), + [ rootClientId ] + ); + const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); + + const [ layout, setLayout ] = useState( + createInitialLayouts( grid, blockClientIds ) + ); + + const lastClickedBlockAppenderIdRef = useRef(); + const blockClientIdsRef = useRef( blockClientIds ); + + useEffect( + () => { + let nextLayout = layout; + + nextLayout = appendNewBlocks( + nextLayout, + lastClickedBlockAppenderIdRef.current, + blockClientIdsRef.current, + blockClientIds + ); + nextLayout = resizeOverflowingBlocks( nextLayout, nodes ); + nextLayout = cropAndFillEmptyCells( nextLayout, cols, rows ); + + if ( layout !== nextLayout ) { + setLayout( nextLayout ); + } + + blockClientIdsRef.current = blockClientIds; + }, + // We reference `grid` here instead of `layouts` to avoid + // potential chained updates when block appenders are being displaced + // and overflows happen. `grid` will only change with + // persistent user changes and not when block appenders + // are displaced. + [ grid, blockClientIds, nodes, cols, rows ] + ); + + return ( +
+ { + setLayout( nextLayout ); + updateBlockAttributes( rootClientId, { + grid: nextLayout.filter( + ( item ) => ! item.i.startsWith( 'block-appender' ) + ), + } ); + } } + rowHeight={ 200 } + verticalCompact={ false } + > + { [ + ...layout + .filter( ( item ) => + item.i.startsWith( 'block-appender' ) + ) + .map( ( item ) => ( +
+ ( lastClickedBlockAppenderIdRef.current = id ) + } + onKeyPress={ ( { currentTarget: { id } } ) => + ( lastClickedBlockAppenderIdRef.current = id ) + } + role="button" + tabIndex="0" + > + +
+ ) ), + ...blockClientIds.map( ( blockClientId, index ) => { + const isBlockInSelection = hasMultiSelection + ? multiSelectedBlockClientIds.includes( + blockClientId + ) + : selectedBlockClientId === blockClientId; + return ( +
+ + + +
+ ); + } ), + ] } +
+
+ ); +} + +BlockGrid.Content = ( { + attributes: { grid, breakpoint = 'small', cols = 2, rows = 2, align }, + innerBlocks, +} ) => { + const gridId = `editor-block-list__grid-content-${ hashGrid( grid ) }`; + return ( +
+ + { `` } + + { grid + .map( ( item, i ) => ( { + item, + element: ( +
+ { /* Guard here, because inner blocks are not available during validation. */ } + { innerBlocks[ i ] && ( + + { serialize( innerBlocks[ i ], { + isInnerBlocks: true, + } ) } + + ) } +
+ ), + } ) ) + .sort( ( a, b ) => { + const rowSort = a.item.y - b.item.y; + if ( rowSort !== 0 ) { + return rowSort; + } + return a.item.x - b.item.x; + } ) + .map( ( item ) => item.element ) } +
+ ); +}; + +export default BlockGrid; diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 8bf433d083fe45..e925b30a2da58e 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -6,8 +6,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { AsyncModeProvider, useSelect } from '@wordpress/data'; +import { AsyncModeProvider, useSelect, useDispatch } from '@wordpress/data'; import { useRef } from '@wordpress/element'; +import { Toolbar } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { BREAKPOINTS } from '@wordpress/viewport'; /** * Internal dependencies @@ -16,6 +19,8 @@ import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; import RootContainer from './root-container'; import useBlockDropZone from '../block-drop-zone'; +import BlockGrid from './grid'; +import BlockControls from '../block-controls'; /** * If the block count exceeds the threshold, we disable the reordering animation @@ -37,6 +42,7 @@ function BlockList( { isDraggable, renderAppender, __experimentalUIParts = {}, + __experimentalGridMode, } ) { function selector( select ) { const { @@ -47,8 +53,9 @@ function BlockList( { hasMultiSelection, getGlobalBlockCount, isTyping, + getBlockAttributes, } = select( 'core/block-editor' ); - + const attributes = getBlockAttributes( rootClientId ) || {}; return { blockClientIds: getBlockOrder( rootClientId ), isMultiSelecting: isMultiSelecting(), @@ -58,6 +65,10 @@ function BlockList( { enableAnimation: ! isTyping() && getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, + attributes, + gridModeEnabled: select( 'core/viewport' ).isViewportMatch( + attributes.breakpoint || 'small' + ), }; } @@ -68,7 +79,10 @@ function BlockList( { multiSelectedBlockClientIds, hasMultiSelection, enableAnimation, + attributes, + gridModeEnabled, } = useSelect( selector, [ rootClientId ] ); + const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); const Container = rootClientId ? 'div' : RootContainer; const ref = useRef(); @@ -80,6 +94,79 @@ function BlockList( { ? {} : { hasPopover: __experimentalUIParts.hasPopover }; + const gridModeControls = __experimentalGridMode ? ( + + ( { + title: `${ _breakpoint } (>= ${ BREAKPOINTS[ _breakpoint ] }px)`, + isActive: attributes.breakpoint === _breakpoint, + onClick: () => + updateBlockAttributes( rootClientId, { + breakpoint: _breakpoint, + } ), + } ) + ) } + isCollapsed + /> + ( { + title: cols, + isActive: attributes.cols === cols, + onClick: () => + updateBlockAttributes( rootClientId, { + cols, + } ), + } ) ) } + isCollapsed + /> + ( { + title: rows, + isActive: attributes.rows === rows, + onClick: () => + updateBlockAttributes( rootClientId, { + rows, + } ), + } ) ) } + isCollapsed + /> + + ) : null; + + if ( __experimentalGridMode && gridModeEnabled ) { + return ( + + + { gridModeControls } + + ); + } + return ( + { gridModeControls } ); } @@ -135,4 +223,6 @@ function BlockList( { // This component needs to always be synchronous // as it's the one changing the async mode // depending on the block selection. -export default forceSyncUpdates( BlockList ); +BlockList = forceSyncUpdates( BlockList ); +BlockList.GridContent = BlockGrid.Content; +export default BlockList; diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index db82de01412fee..61752134ea70b4 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -582,3 +582,38 @@ opacity: 0; } } + +/** + * Block Grid + */ +@import "node_modules/react-grid-layout/css/styles"; +@import "node_modules/react-resizable/css/styles"; + +.block-editor-block-list__grid { + overflow: scroll; + + .react-grid-item.react-grid-placeholder { + background-color: $blue-medium-highlight; + } + + .react-grid-item > .react-resizable-handle { + height: 40px; + width: 40px; + } + + .block-list-appender { + &.block-list-appender { + margin: 0; + } + + &, + & > *, + & > * > * { + height: 100%; + } + } +} + +.block-editor-block-list__grid-item { + padding: 0 20px; +} diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index c8a54479d420df..3f57a47799c539 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -232,9 +232,14 @@ InnerBlocks = compose( [ InnerBlocks.DefaultBlockAppender = DefaultBlockAppender; InnerBlocks.ButtonBlockAppender = ButtonBlockAppender; -InnerBlocks.Content = withBlockContentContext( ( { BlockContent } ) => ( - -) ); +InnerBlocks.Content = withBlockContentContext( + ( { BlockContent, __experimentalGridMode } ) => + __experimentalGridMode ? ( + + ) : ( + + ) +); /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md diff --git a/packages/block-library/src/grid/block.json b/packages/block-library/src/grid/block.json new file mode 100644 index 00000000000000..8e8710c4575d7e --- /dev/null +++ b/packages/block-library/src/grid/block.json @@ -0,0 +1,23 @@ +{ + "name": "core/grid", + "category": "layout", + "supports": { "align": true }, + "attributes": { + "grid": { + "type": "array", + "default": [] + }, + "breakpoint": { + "type": "string", + "default": "small" + }, + "cols": { + "type": "number", + "default": 2 + }, + "rows": { + "type": "number", + "default": 2 + } + } +} diff --git a/packages/block-library/src/grid/edit.js b/packages/block-library/src/grid/edit.js new file mode 100644 index 00000000000000..da29a3694ca327 --- /dev/null +++ b/packages/block-library/src/grid/edit.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function GridEdit() { + return ; +} diff --git a/packages/block-library/src/grid/index.js b/packages/block-library/src/grid/index.js new file mode 100644 index 00000000000000..fc02aee4c3377e --- /dev/null +++ b/packages/block-library/src/grid/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import save from './save'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Grid' ), + edit, + save, +}; diff --git a/packages/block-library/src/grid/save.js b/packages/block-library/src/grid/save.js new file mode 100644 index 00000000000000..7e266f0ea0cd39 --- /dev/null +++ b/packages/block-library/src/grid/save.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function GridSave() { + return ; +} diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 1181bbceadfde8..fd41aba8890af0 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -48,6 +48,7 @@ import * as pullquote from './pullquote'; import * as reusableBlock from './block'; import * as rss from './rss'; import * as search from './search'; +import * as grid from './grid'; import * as group from './group'; import * as separator from './separator'; import * as shortcode from './shortcode'; @@ -128,6 +129,7 @@ export const registerCoreBlocks = () => { ...embed.common, ...embed.others, file, + grid, group, window.wp && window.wp.oldEditor ? classic : null, // Only add the classic block in WP Context html, diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 394a212dd88e9e..fcf61d24ebeef5 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -134,7 +134,7 @@ export function getSaveElement( ); return ( - + { element } ); diff --git a/packages/blocks/src/block-content-provider/index.js b/packages/blocks/src/block-content-provider/index.js index 505fd4adeeacdd..9e81b2f8a0b6c9 100644 --- a/packages/blocks/src/block-content-provider/index.js +++ b/packages/blocks/src/block-content-provider/index.js @@ -28,7 +28,7 @@ const { Consumer, Provider } = createContext( () => {} ); * * @return {WPComponent} Element with BlockContent injected via context. */ -const BlockContentProvider = ( { children, innerBlocks } ) => { +const BlockContentProvider = ( { children, attributes, innerBlocks } ) => { const BlockContent = () => { // Value is an array of blocks, so defer to block serializer const html = serialize( innerBlocks, { isInnerBlocks: true } ); @@ -36,6 +36,8 @@ const BlockContentProvider = ( { children, innerBlocks } ) => { // Use special-cased raw HTML tag to avoid default escaping return { html }; }; + BlockContent.attributes = attributes; + BlockContent.innerBlocks = innerBlocks; return { children }; }; diff --git a/packages/viewport/README.md b/packages/viewport/README.md index 320f46daf8d431..9340cca71231eb 100644 --- a/packages/viewport/README.md +++ b/packages/viewport/README.md @@ -49,6 +49,18 @@ This package provides a set of HOCs to author components whose behavior should v +# **BREAKPOINTS** + +Hash of breakpoint names with pixel width at which it becomes effective. + +_Related_ + +- \_breakpoints.scss + +_Type_ + +- `Object` + # **ifViewportMatches** Higher-order component creator, creating a new component which renders if diff --git a/packages/viewport/src/index.js b/packages/viewport/src/index.js index b2faa967569480..77fb6718afe98c 100644 --- a/packages/viewport/src/index.js +++ b/packages/viewport/src/index.js @@ -14,7 +14,7 @@ export { default as withViewportMatch } from './with-viewport-match'; * * @type {Object} */ -const BREAKPOINTS = { +export const BREAKPOINTS = { huge: 1440, wide: 1280, large: 960,