-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Background image: Add backgroundSize and repeat features #57005
Changes from all commits
7a0cf36
cab063a
3a42f39
d9a8f7d
f09efb8
53e38e3
5411b3d
851c607
c84fc8a
b01202c
becb2e8
10ac7f8
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 |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
/** | ||
* Temporary compatibility shims for block APIs present in Gutenberg. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Update allowed inline style attributes list. | ||
* | ||
* @param string[] $attrs Array of allowed CSS attributes. | ||
* @return string[] CSS attributes. | ||
*/ | ||
function gutenberg_safe_style_attrs_6_5( $attrs ) { | ||
$attrs[] = 'background-repeat'; | ||
return $attrs; | ||
} | ||
add_filter( 'safe_style_css', 'gutenberg_safe_style_attrs_6_5' ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,12 @@ import { isBlobURL } from '@wordpress/blob'; | |
import { getBlockSupport } from '@wordpress/blocks'; | ||
import { focus } from '@wordpress/dom'; | ||
import { | ||
ToggleControl, | ||
__experimentalToggleGroupControl as ToggleGroupControl, | ||
__experimentalToggleGroupControlOption as ToggleGroupControlOption, | ||
__experimentalToolsPanelItem as ToolsPanelItem, | ||
__experimentalUnitControl as UnitControl, | ||
__experimentalVStack as VStack, | ||
DropZone, | ||
FlexItem, | ||
MenuItem, | ||
|
@@ -52,6 +57,17 @@ export function hasBackgroundImageValue( style ) { | |
return hasValue; | ||
} | ||
|
||
/** | ||
* Checks if there is a current value in the background size block support | ||
* attributes. | ||
* | ||
* @param {Object} style Style attribute. | ||
* @return {boolean} Whether or not the block has a background size value set. | ||
*/ | ||
export function hasBackgroundSizeValue( style ) { | ||
return style?.background?.backgroundSize !== undefined; | ||
} | ||
|
||
/** | ||
* Determine whether there is block support for background. | ||
* | ||
|
@@ -72,7 +88,11 @@ export function hasBackgroundSupport( blockName, feature = 'any' ) { | |
} | ||
|
||
if ( feature === 'any' ) { | ||
return !! support?.backgroundImage; | ||
return ( | ||
!! support?.backgroundImage || | ||
!! support?.backgroundSize || | ||
!! support?.backgroundRepeat | ||
); | ||
} | ||
|
||
return !! support?.[ feature ]; | ||
|
@@ -97,6 +117,26 @@ export function resetBackgroundImage( style = {}, setAttributes ) { | |
} ); | ||
} | ||
|
||
/** | ||
* Resets the background size block support attributes. This can be used when disabling | ||
* the background size controls for a block via a `ToolsPanel`. | ||
* | ||
* @param {Object} style Style attribute. | ||
* @param {Function} setAttributes Function to set block's attributes. | ||
*/ | ||
function resetBackgroundSize( style = {}, setAttributes ) { | ||
setAttributes( { | ||
style: cleanEmptyObject( { | ||
...style, | ||
background: { | ||
...style?.background, | ||
backgroundRepeat: undefined, | ||
backgroundSize: undefined, | ||
}, | ||
} ), | ||
} ); | ||
} | ||
|
||
function InspectorImagePreview( { label, filename, url: imgUrl } ) { | ||
const imgLabel = label || getFilename( imgUrl ); | ||
return ( | ||
|
@@ -142,7 +182,11 @@ function InspectorImagePreview( { label, filename, url: imgUrl } ) { | |
); | ||
} | ||
|
||
function BackgroundImagePanelItem( { clientId, setAttributes } ) { | ||
function BackgroundImagePanelItem( { | ||
clientId, | ||
isShownByDefault, | ||
setAttributes, | ||
} ) { | ||
const { style, mediaUpload } = useSelect( | ||
( select ) => { | ||
const { getBlockAttributes, getSettings } = | ||
|
@@ -252,7 +296,7 @@ function BackgroundImagePanelItem( { clientId, setAttributes } ) { | |
hasValue={ () => hasValue } | ||
label={ __( 'Background image' ) } | ||
onDeselect={ () => resetBackgroundImage( style, setAttributes ) } | ||
isShownByDefault={ true } | ||
isShownByDefault={ isShownByDefault } | ||
resetAllFilter={ resetAllFilter } | ||
panelId={ clientId } | ||
> | ||
|
@@ -302,18 +346,200 @@ function BackgroundImagePanelItem( { clientId, setAttributes } ) { | |
); | ||
} | ||
|
||
function backgroundSizeHelpText( value ) { | ||
if ( value === 'cover' || value === undefined ) { | ||
return __( 'Stretch image to cover the block.' ); | ||
} | ||
if ( value === 'contain' ) { | ||
return __( 'Resize image to fit without cropping.' ); | ||
} | ||
return __( 'Set a fixed width.' ); | ||
} | ||
|
||
function BackgroundSizePanelItem( { | ||
clientId, | ||
isShownByDefault, | ||
setAttributes, | ||
} ) { | ||
const style = useSelect( | ||
( select ) => | ||
select( blockEditorStore ).getBlockAttributes( clientId )?.style, | ||
[ clientId ] | ||
); | ||
|
||
const sizeValue = style?.background?.backgroundSize; | ||
const repeatValue = style?.background?.backgroundRepeat; | ||
|
||
// An `undefined` value is treated as `cover` by the toggle group control. | ||
// An empty string is treated as `auto` by the toggle group control. This | ||
// allows a user to select "Size" and then enter a custom value, with an | ||
// empty value being treated as `auto`. | ||
const currentValueForToggle = | ||
( sizeValue !== undefined && | ||
sizeValue !== 'cover' && | ||
sizeValue !== 'contain' ) || | ||
sizeValue === '' | ||
? 'auto' | ||
: sizeValue || 'cover'; | ||
|
||
// If the current value is `cover` and the repeat value is `undefined`, then | ||
// the toggle should be unchecked as the default state. Otherwise, the toggle | ||
// should reflect the current repeat value. | ||
const repeatCheckedValue = | ||
repeatValue === 'no-repeat' || | ||
( currentValueForToggle === 'cover' && repeatValue === undefined ) | ||
? false | ||
: true; | ||
|
||
const hasValue = hasBackgroundSizeValue( style ); | ||
|
||
const resetAllFilter = useCallback( ( previousValue ) => { | ||
return { | ||
...previousValue, | ||
style: { | ||
...previousValue.style, | ||
background: { | ||
...previousValue.style?.background, | ||
backgroundRepeat: undefined, | ||
backgroundSize: undefined, | ||
}, | ||
}, | ||
}; | ||
}, [] ); | ||
|
||
const updateBackgroundSize = ( next ) => { | ||
// When switching to 'contain' toggle the repeat off. | ||
let nextRepeat = repeatValue; | ||
|
||
if ( next === 'contain' ) { | ||
nextRepeat = 'no-repeat'; | ||
} | ||
|
||
if ( | ||
( currentValueForToggle === 'cover' || | ||
currentValueForToggle === 'contain' ) && | ||
next === 'auto' | ||
) { | ||
nextRepeat = undefined; | ||
} | ||
|
||
setAttributes( { | ||
style: cleanEmptyObject( { | ||
...style, | ||
background: { | ||
...style?.background, | ||
backgroundRepeat: nextRepeat, | ||
backgroundSize: next, | ||
}, | ||
} ), | ||
} ); | ||
}; | ||
|
||
const toggleIsRepeated = () => { | ||
setAttributes( { | ||
style: cleanEmptyObject( { | ||
...style, | ||
background: { | ||
...style?.background, | ||
backgroundRepeat: | ||
repeatCheckedValue === true ? 'no-repeat' : undefined, | ||
}, | ||
} ), | ||
} ); | ||
}; | ||
|
||
return ( | ||
<VStack | ||
as={ ToolsPanelItem } | ||
spacing={ 2 } | ||
className="single-column" | ||
hasValue={ () => hasValue } | ||
label={ __( 'Size' ) } | ||
onDeselect={ () => resetBackgroundSize( style, setAttributes ) } | ||
isShownByDefault={ isShownByDefault } | ||
resetAllFilter={ resetAllFilter } | ||
panelId={ clientId } | ||
> | ||
<ToggleGroupControl | ||
__nextHasNoMarginBottom | ||
size={ '__unstable-large' } | ||
label={ __( 'Size' ) } | ||
value={ currentValueForToggle } | ||
onChange={ updateBackgroundSize } | ||
isBlock={ true } | ||
help={ backgroundSizeHelpText( sizeValue ) } | ||
> | ||
<ToggleGroupControlOption | ||
key={ 'cover' } | ||
value={ 'cover' } | ||
label={ __( 'Cover' ) } | ||
/> | ||
<ToggleGroupControlOption | ||
key={ 'contain' } | ||
value={ 'contain' } | ||
label={ __( 'Contain' ) } | ||
/> | ||
<ToggleGroupControlOption | ||
key={ 'fixed' } | ||
value={ 'auto' } | ||
label={ __( 'Fixed' ) } | ||
/> | ||
</ToggleGroupControl> | ||
{ sizeValue !== undefined && | ||
sizeValue !== 'cover' && | ||
sizeValue !== 'contain' ? ( | ||
<UnitControl | ||
size={ '__unstable-large' } | ||
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. Definitely a 'later' thing, but do you reckon it'd be useful follow up to have width and height controls? E.g., be able to produce values like 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. Yes — I was imagining in follow-ups we could introduce a split control. I imagine in most cases folks will want to only use a single size value so that the image's proportions are maintained. |
||
onChange={ updateBackgroundSize } | ||
value={ sizeValue } | ||
/> | ||
) : null } | ||
{ currentValueForToggle !== 'cover' && ( | ||
<ToggleControl | ||
__nextHasNoMarginBottom | ||
label={ __( 'Repeat image' ) } | ||
checked={ repeatCheckedValue } | ||
onChange={ toggleIsRepeated } | ||
/> | ||
) } | ||
</VStack> | ||
); | ||
} | ||
|
||
export function BackgroundImagePanel( props ) { | ||
const [ backgroundImage ] = useSettings( 'background.backgroundImage' ); | ||
const [ backgroundImage, backgroundSize ] = useSettings( | ||
'background.backgroundImage', | ||
'background.backgroundSize' | ||
); | ||
|
||
if ( | ||
! backgroundImage || | ||
! hasBackgroundSupport( props.name, 'backgroundImage' ) | ||
) { | ||
return null; | ||
} | ||
|
||
const showBackgroundSize = !! ( | ||
backgroundSize && hasBackgroundSupport( props.name, 'backgroundSize' ) | ||
); | ||
|
||
const defaultControls = getBlockSupport( props.name, [ | ||
BACKGROUND_SUPPORT_KEY, | ||
'__experimentalDefaultControls', | ||
] ); | ||
|
||
return ( | ||
<InspectorControls group="background"> | ||
<BackgroundImagePanelItem { ...props } /> | ||
<BackgroundImagePanelItem | ||
isShownByDefault={ defaultControls?.backgroundImage } | ||
{ ...props } | ||
/> | ||
{ showBackgroundSize && ( | ||
<BackgroundSizePanelItem | ||
isShownByDefault={ defaultControls?.backgroundSize } | ||
{ ...props } | ||
/> | ||
) } | ||
</InspectorControls> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's a bool, also possible to do something like:
Just an observation. No need to change if it works! 😄