Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 79 additions & 57 deletions packages/block-editor/src/hooks/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,9 @@ import {
useHasColorPanel,
default as StylesColorPanel,
} from '../components/global-styles/color-panel';
import { extractColorSlug } from '../utils/color-values';
import BlockColorContrastChecker from './contrast-checker';
import PseudoStateContrastChecker from './pseudo-state-contrast-checker';
import { store as blockEditorStore } from '../store';
import {
getStyleForState,
setStyleForState,
useBlockStyleState,
} from './block-style-state';

export const COLOR_SUPPORT_KEY = 'color';

Expand Down Expand Up @@ -69,7 +64,6 @@ const hasLinkColorSupport = ( blockType ) => {

const hasGradientSupport = ( blockNameOrType ) => {
const colorSupport = getBlockSupport( blockNameOrType, COLOR_SUPPORT_KEY );

return (
colorSupport !== null &&
typeof colorSupport === 'object' &&
Expand All @@ -79,13 +73,11 @@ const hasGradientSupport = ( blockNameOrType ) => {

const hasBackgroundColorSupport = ( blockType ) => {
const colorSupport = getBlockSupport( blockType, COLOR_SUPPORT_KEY );

return colorSupport && colorSupport.background !== false;
};

const hasTextColorSupport = ( blockType ) => {
const colorSupport = getBlockSupport( blockType, COLOR_SUPPORT_KEY );

return colorSupport && colorSupport.text !== false;
};

Expand Down Expand Up @@ -197,9 +189,17 @@ export function addSaveProps( props, blockNameOrType, attributes ) {

function styleToAttributes( style ) {
const textColorValue = style?.color?.text;
const textColorSlug = extractColorSlug( textColorValue );
const textColorSlug = textColorValue?.startsWith( 'var:preset|color|' )
? textColorValue.substring( 'var:preset|color|'.length )
: undefined;

const backgroundColorValue = style?.color?.background;
const backgroundColorSlug = extractColorSlug( backgroundColorValue );
const backgroundColorSlug = backgroundColorValue?.startsWith(
'var:preset|color|'
)
? backgroundColorValue.substring( 'var:preset|color|'.length )
: undefined;

const gradientValue = style?.color?.gradient;
const gradientSlug = gradientValue?.startsWith( 'var:preset|gradient|' )
? gradientValue.substring( 'var:preset|gradient|'.length )
Expand Down Expand Up @@ -260,6 +260,24 @@ function ColorInspectorControl( { children, resetAllFilter } ) {
);
}

/**
* @typedef {'default'|':hover'|':focus'|':focus-visible'|':active'} PseudoState
*/

/**
* Renders the color inspector controls for a block, including per-pseudo-state
* contrast checking when an interactive state panel is active.
*
* @param {Object} props
* @param {string} props.clientId Block client ID.
* @param {string} props.name Block name.
* @param {Function} props.setAttributes Block setAttributes.
* @param {Object} props.settings Color settings passed from block support.
* @param {React.ElementType} [props.asWrapper] Optional custom wrapper.
* @param {string} [props.label] Panel label.
* @param {Object} [props.defaultControls] Which controls are open by default.
* @param {PseudoState} [props.activePseudoState] Active interactive state, e.g. ':hover'.
*/
export function ColorEdit( {
clientId,
name,
Expand All @@ -268,10 +286,9 @@ export function ColorEdit( {
asWrapper,
label,
defaultControls,
activePseudoState,
} ) {
const selectedState = useBlockStyleState();
const isEnabled = useHasColorPanel( settings );

const { style, textColor, backgroundColor, gradient } = useSelect(
( select ) => {
// Early return to avoid subscription when disabled
Expand All @@ -294,38 +311,18 @@ export function ColorEdit( {
[ clientId, isEnabled ]
);

const isStateSelected = selectedState !== 'default';

const value = useMemo( () => {
if ( isStateSelected ) {
return getStyleForState( style, selectedState );
}
return attributesToStyle( {
style,
textColor,
backgroundColor,
gradient,
} );
}, [
isStateSelected,
selectedState,
style,
textColor,
backgroundColor,
gradient,
] );

const onChange = isStateSelected
? ( newStyle ) => {
setAttributes( {
style: setStyleForState( style, selectedState, newStyle ),
} );
}
: ( newStyle ) => {
setAttributes( styleToAttributes( newStyle ) );
};
}, [ style, textColor, backgroundColor, gradient ] );

const Wrapper = asWrapper || ColorInspectorControl;
const onChange = ( newStyle ) => {
setAttributes( styleToAttributes( newStyle ) );
};

if ( ! isEnabled ) {
return null;
Expand All @@ -338,19 +335,27 @@ export function ColorEdit( {
'__experimentalDefaultControls',
] );

const contrastCheckerBlockSupportEnabled =
false !==
getBlockSupport( name, [ COLOR_SUPPORT_KEY, 'enableContrastChecker' ] );

// Determine whether to show contrast checking at all.
const enableContrastChecking =
! isStateSelected &&
Platform.OS === 'web' &&
! value?.color?.gradient &&
( settings?.color?.text || settings?.color?.link ) &&
// Contrast checking is enabled by default.
// Deactivating it requires `enableContrastChecker` to have
// an explicit value of `false`.
false !==
getBlockSupport( name, [
COLOR_SUPPORT_KEY,
'enableContrastChecker',
] );
contrastCheckerBlockSupportEnabled;

/**
* Whether the user is currently viewing a pseudo-state panel (e.g. ':hover')
* rather than the default state. The sentinel string 'default' is treated as
* no active state to keep the API simple for callers.
*/
const isEditingPseudoState =
activePseudoState && activePseudoState !== 'default';

// Use provided wrapper or default to ColorInspectorControl.
const Wrapper = asWrapper || ColorInspectorControl;

return (
<StylesColorPanel
Expand All @@ -361,19 +366,36 @@ export function ColorEdit( {
onChange={ onChange }
defaultControls={ defaultControls }
label={ label }
enableContrastChecker={
false !==
getBlockSupport( name, [
COLOR_SUPPORT_KEY,
'enableContrastChecker',
] )
}
enableContrastChecker={ contrastCheckerBlockSupportEnabled }
>
{ enableContrastChecking && (
<BlockColorContrastChecker
clientId={ clientId }
name={ name }
/>
<>
{ /*
* For pseudo-state panels, use the attribute-based checker.
* For the default-state panel (or no active state), use the
* existing DOM-based checker.
*
* This also fixes the "false hover warning" described in #78305:
* when the user is on the default panel and physically hovers the
* canvas element, the DOM checker would previously read hover-state
* computed styles and emit spurious warnings. By gating the
* DOM-based checker on `!isEditingPseudoState` and having it only
* fire when no pseudo-state is active, we guarantee it always sees
* the resting-state colors.
*/ }
{ isEditingPseudoState ? (
<PseudoStateContrastChecker
clientId={ clientId }
name={ name }
activePseudoState={ activePseudoState }
/>
) : (
<BlockColorContrastChecker
clientId={ clientId }
name={ name }
/>
) }
</>
) }
</StylesColorPanel>
);
Expand Down
Loading
Loading