-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Block spacing: block-level axial gap block support #37736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1f7a1be
9ff0734
72021d8
de6da58
be909d7
34b7f8c
2191831
119333b
765ca94
b618d4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { Platform } from '@wordpress/element'; | |
| import { getBlockSupport } from '@wordpress/blocks'; | ||
| import { | ||
| __experimentalUseCustomUnits as useCustomUnits, | ||
| __experimentalBoxControl as BoxControl, | ||
| __experimentalUnitControl as UnitControl, | ||
| } from '@wordpress/components'; | ||
|
|
||
|
|
@@ -14,14 +15,14 @@ import { | |
| */ | ||
| import { __unstableUseBlockRef as useBlockRef } from '../components/block-list/use-block-props/use-block-refs'; | ||
| import useSetting from '../components/use-setting'; | ||
| import { SPACING_SUPPORT_KEY } from './dimensions'; | ||
| import { AXIAL_SIDES, SPACING_SUPPORT_KEY, useCustomSides } from './dimensions'; | ||
| import { cleanEmptyObject } from './utils'; | ||
|
|
||
| /** | ||
| * Determines if there is gap support. | ||
| * | ||
| * @param {string|Object} blockType Block name or Block Type object. | ||
| * @return {boolean} Whether there is support. | ||
| * @return {boolean} Whether there is support. | ||
| */ | ||
| export function hasGapSupport( blockType ) { | ||
| const support = getBlockSupport( blockType, SPACING_SUPPORT_KEY ); | ||
|
|
@@ -38,6 +39,45 @@ export function hasGapValue( props ) { | |
| return props.attributes.style?.spacing?.blockGap !== undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Returns a BoxControl object value from a given blockGap style. | ||
| * The string check is for backwards compatibility before Gutenberg supported | ||
| * split gap values (row and column) and the value was a string n + unit. | ||
| * | ||
| * @param {string? | Object?} rawBlockGapValue A style object. | ||
| * @return {Object?} A value to pass to the BoxControl component. | ||
| */ | ||
| export function getGapValueFromStyle( rawBlockGapValue ) { | ||
| if ( ! rawBlockGapValue ) { | ||
| return rawBlockGapValue; | ||
| } | ||
|
|
||
| const isValueString = typeof rawBlockGapValue === 'string'; | ||
| return { | ||
| top: isValueString ? rawBlockGapValue : rawBlockGapValue?.top, | ||
| left: isValueString ? rawBlockGapValue : rawBlockGapValue?.left, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Returns a CSS value for the `gap` property from a given blockGap style. | ||
| * | ||
| * @param {string? | Object?} blockGapValue A style object. | ||
| * @param {string?} defaultValue A default gap value. | ||
| * @return {string|null} The concatenated gap value (row and column). | ||
| */ | ||
| export function getGapCSSValue( blockGapValue, defaultValue = '0' ) { | ||
| const blockGapBoxControlValue = getGapValueFromStyle( blockGapValue ); | ||
| if ( ! blockGapBoxControlValue ) { | ||
| return null; | ||
| } | ||
|
|
||
| const row = blockGapBoxControlValue?.top || defaultValue; | ||
| const column = blockGapBoxControlValue?.left || defaultValue; | ||
|
|
||
| return row === column ? row : `${ row } ${ column }`; | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I was wondering whether we should be returning the full definition so that we can isolate row and column values where only one is set, e.g., // row and column is set or is the same
return `gap: 10px 10px;`;
// only row is set
return `row-gap: 10px;`;
// only column is set
return `column-gap: 10px;`;That way, if a block ever has to inherit either row/column we're not overwriting it. Just typing out loud...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be a nice feature, though if we do it here we should do it on the server-side too 🙂
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After some further tinkering I probably need to put more thought into this. I'm picturing a future where theme.json authors could add something like this. "spacing": {
"blockGap": {
"row": "0.5em",
"column": "2em"
}
}and blocks would then inherit or use these values as defaults. Currently "blockGap" accept a string, which can be really anything, including the shorthand value of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, we don't need it for this PR; better to work out the details as a future enhancement. |
||
|
|
||
| /** | ||
| * Resets the gap block support attribute. This can be used when disabling | ||
| * the gap support controls for a block via a progressive discovery panel. | ||
|
|
@@ -82,6 +122,7 @@ export function GapEdit( props ) { | |
| const { | ||
| clientId, | ||
| attributes: { style }, | ||
| name: blockName, | ||
| setAttributes, | ||
| } = props; | ||
|
|
||
|
|
@@ -94,7 +135,7 @@ export function GapEdit( props ) { | |
| 'vw', | ||
| ], | ||
| } ); | ||
|
|
||
| const sides = useCustomSides( blockName, 'blockGap' ); | ||
| const ref = useBlockRef( clientId ); | ||
|
|
||
| if ( useIsGapDisabled( props ) ) { | ||
|
|
@@ -106,7 +147,9 @@ export function GapEdit( props ) { | |
| ...style, | ||
| spacing: { | ||
| ...style?.spacing, | ||
| blockGap: next, | ||
| blockGap: { | ||
| ...getGapValueFromStyle( next ), | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
|
|
@@ -128,17 +171,45 @@ export function GapEdit( props ) { | |
| } | ||
| }; | ||
|
|
||
| const splitOnAxis = | ||
| sides && sides.some( ( side ) => AXIAL_SIDES.includes( side ) ); | ||
| const gapValue = getGapValueFromStyle( style?.spacing?.blockGap ); | ||
|
|
||
| // The BoxControl component expects a full complement of side values. | ||
| // Gap row and column values translate to top/bottom and left/right respectively. | ||
| const boxControlGapValue = splitOnAxis | ||
| ? { | ||
| ...gapValue, | ||
| right: gapValue?.left, | ||
| bottom: gapValue?.top, | ||
ramonjd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| : gapValue?.top; | ||
|
|
||
| return Platform.select( { | ||
| web: ( | ||
| <> | ||
| <UnitControl | ||
| label={ __( 'Block spacing' ) } | ||
| __unstableInputWidth="80px" | ||
| min={ 0 } | ||
| onChange={ onChange } | ||
| units={ units } | ||
| value={ style?.spacing?.blockGap } | ||
| /> | ||
| { splitOnAxis ? ( | ||
| <BoxControl | ||
| label={ __( 'Block spacing' ) } | ||
| min={ 0 } | ||
| onChange={ onChange } | ||
| units={ units } | ||
| sides={ sides } | ||
| values={ boxControlGapValue } | ||
| allowReset={ false } | ||
| splitOnAxis={ splitOnAxis } | ||
| /> | ||
| ) : ( | ||
| <UnitControl | ||
| label={ __( 'Block spacing' ) } | ||
| __unstableInputWidth="80px" | ||
| min={ 0 } | ||
| onChange={ onChange } | ||
| units={ units } | ||
| // Default to `row` for combined values. | ||
| value={ boxControlGapValue } | ||
| /> | ||
| ) } | ||
| </> | ||
| ), | ||
| native: null, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { getGapCSSValue } from '../gap'; | ||
|
|
||
| describe( 'gap', () => { | ||
| describe( 'getGapCSSValue()', () => { | ||
| it( 'should return `null` if argument is falsey', () => { | ||
| expect( getGapCSSValue( undefined ) ).toBeNull(); | ||
| expect( getGapCSSValue( '' ) ).toBeNull(); | ||
| } ); | ||
|
|
||
| it( 'should return single value for gap if argument is valid string', () => { | ||
| expect( getGapCSSValue( '88rem' ) ).toEqual( '88rem' ); | ||
| } ); | ||
|
|
||
| it( 'should return single value for gap if row and column are the same', () => { | ||
| const blockGapValue = { | ||
| top: '88rem', | ||
| left: '88rem', | ||
| }; | ||
| expect( getGapCSSValue( blockGapValue ) ).toEqual( '88rem' ); | ||
| } ); | ||
|
|
||
| it( 'should return shorthand value for gap if row and column are different', () => { | ||
| const blockGapValue = { | ||
| top: '88px', | ||
| left: '88rem', | ||
| }; | ||
| expect( getGapCSSValue( blockGapValue ) ).toEqual( '88px 88rem' ); | ||
| } ); | ||
|
|
||
| it( 'should return default value if a top or left is missing', () => { | ||
| const blockGapValue = { | ||
| top: '88px', | ||
| }; | ||
| expect( getGapCSSValue( blockGapValue, '1px' ) ).toEqual( | ||
| '88px 1px' | ||
| ); | ||
| } ); | ||
| } ); | ||
| } ); |
Uh oh!
There was an error while loading. Please reload this page.