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
4 changes: 4 additions & 0 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Bug Fixes

- `ColorPanel`: Theme CSS custom-property gradients are now decoded to their preset slug and persisted as a `gradient` block attribute rather than as a raw `style.color.gradient` value ([#78328](https://github.com/WordPress/gutenberg/pull/78328)).

## 15.19.0 (2026-05-14)

### Enhancements
Expand Down
32 changes: 19 additions & 13 deletions packages/block-editor/src/components/global-styles/color-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import ColorGradientControl from '../colors-gradients/control';
import { useColorsPerOrigin, useGradientsPerOrigin } from './hooks';
import { useToolsPanelDropdownMenuProps } from './utils';
import { setImmutably } from '../../utils/object';
import { extractColorSlug } from '../../utils/color-values';
import { extractPresetSlug } from '../../utils/color-values';
import { unlock } from '../../lock-unlock';

export function useHasColorPanel( settings ) {
Expand Down Expand Up @@ -605,8 +605,9 @@ export default function ColorPanel( {
key: 'text',
label: __( 'Text' ),
inheritedValue: textColor,
inheritedSlug: extractColorSlug(
inheritedValue?.color?.text
inheritedSlug: extractPresetSlug(
inheritedValue?.color?.text,
'color'
),
setValue: setTextColor,
userValue: userTextColor,
Expand All @@ -628,8 +629,9 @@ export default function ColorPanel( {
key: 'background',
label: __( 'Color' ),
inheritedValue: backgroundColor,
inheritedSlug: extractColorSlug(
inheritedValue?.color?.background
inheritedSlug: extractPresetSlug(
inheritedValue?.color?.background,
'color'
),
setValue: setBackgroundColor,
userValue: userBackgroundColor,
Expand All @@ -656,8 +658,9 @@ export default function ColorPanel( {
key: 'link',
label: __( 'Default' ),
inheritedValue: linkColor,
inheritedSlug: extractColorSlug(
inheritedValue?.elements?.link?.color?.text
inheritedSlug: extractPresetSlug(
inheritedValue?.elements?.link?.color?.text,
'color'
),
setValue: setLinkColor,
userValue: userLinkColor,
Expand All @@ -666,9 +669,10 @@ export default function ColorPanel( {
key: 'hover',
label: __( 'Hover' ),
inheritedValue: hoverLinkColor,
inheritedSlug: extractColorSlug(
inheritedSlug: extractPresetSlug(
inheritedValue?.elements?.link?.[ ':hover' ]?.color
?.text
?.text,
'color'
),
setValue: setHoverLinkColor,
userValue: userHoverLinkColor,
Expand Down Expand Up @@ -780,8 +784,9 @@ export default function ColorPanel( {
key: 'text',
label: __( 'Text' ),
inheritedValue: elementTextColor,
inheritedSlug: extractColorSlug(
inheritedValue?.elements?.[ name ]?.color?.text
inheritedSlug: extractPresetSlug(
inheritedValue?.elements?.[ name ]?.color?.text,
'color'
),
setValue: setElementTextColor,
userValue: elementTextUserColor,
Expand All @@ -791,9 +796,10 @@ export default function ColorPanel( {
key: 'background',
label: __( 'Background' ),
inheritedValue: elementBackgroundColor,
inheritedSlug: extractColorSlug(
inheritedSlug: extractPresetSlug(
inheritedValue?.elements?.[ name ]?.color
?.background
?.background,
'color'
),
setValue: setElementBackgroundColor,
userValue: elementBackgroundUserColor,
Expand Down
13 changes: 7 additions & 6 deletions packages/block-editor/src/hooks/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
useHasColorPanel,
default as StylesColorPanel,
} from '../components/global-styles/color-panel';
import { extractColorSlug } from '../utils/color-values';
import { extractPresetSlug } from '../utils/color-values';
import BlockColorContrastChecker from './contrast-checker';
import { store as blockEditorStore } from '../store';
import {
Expand Down Expand Up @@ -197,13 +197,14 @@ export function addSaveProps( props, blockNameOrType, attributes ) {

function styleToAttributes( style ) {
const textColorValue = style?.color?.text;
const textColorSlug = extractColorSlug( textColorValue );
const textColorSlug = extractPresetSlug( textColorValue, 'color' );
const backgroundColorValue = style?.color?.background;
const backgroundColorSlug = extractColorSlug( backgroundColorValue );
const backgroundColorSlug = extractPresetSlug(
backgroundColorValue,
'color'
);
const gradientValue = style?.color?.gradient;
const gradientSlug = gradientValue?.startsWith( 'var:preset|gradient|' )
? gradientValue.substring( 'var:preset|gradient|'.length )
: undefined;
const gradientSlug = extractPresetSlug( gradientValue, 'gradient' );
const updatedStyle = { ...style };
updatedStyle.color = {
...updatedStyle.color,
Expand Down
23 changes: 13 additions & 10 deletions packages/block-editor/src/utils/color-values.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
/**
* Extracts the palette slug from a style value, supporting both the user
* preset format and the theme CSS-variable format:
* Extracts the palette slug from a style value for any preset type, supporting
* both the user preset format and the theme CSS-variable format:
*
* - User format: `var:preset|color|slug`
* - Theme format: `var(--wp--preset--color--slug)`
* - User format: `var:preset|<type>|slug`
* - Theme format: `var(--wp--preset--<type>--slug)`
*
* Returns `undefined` for plain hex values, non-strings, or any other
* Returns `undefined` for plain values, non-strings, or any other
* unrecognised format.
*
* @param {*} rawValue Raw style value stored in the style object.
* @param {*} rawValue Raw style value stored in the style object.
* @param {'color'|'gradient'} type Preset type, e.g. `'color'` or `'gradient'`.
* @return {string|undefined} The palette slug, or undefined.
*/
export function extractColorSlug( rawValue ) {
export function extractPresetSlug( rawValue, type ) {
if ( typeof rawValue !== 'string' ) {
return undefined;
}
if ( rawValue.startsWith( 'var:preset|color|' ) ) {
return rawValue.slice( 'var:preset|color|'.length );
const userPrefix = `var:preset|${ type }|`;
if ( rawValue.startsWith( userPrefix ) ) {
return rawValue.slice( userPrefix.length );
}
const cssVarPrefix = `--wp--preset--${ type }--`;
const themeFormatMatch = rawValue.match(
/^var\(--wp--preset--color--([^)]+)\)$/
new RegExp( `^var\\(${ cssVarPrefix }([^)]+)\\)$` )
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nitty, but we could cache the RegExp instead of creating a new one every time. eg

const themeRegexCache = new Map();
function themeRegex( type ) {
	if ( ! themeRegexCache.has( type ) ) {
		themeRegexCache.set(
			type,
			new RegExp( `^var\\(--wp--preset--${ type }--([^)]+)\\)$` )
		);
	}
	return themeRegexCache.get( type );
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should I bound the map or use a caching strategy like LRU so that we don't have an increasing memory from caching the regexes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @ciampo

);
return themeFormatMatch?.[ 1 ];
}
73 changes: 54 additions & 19 deletions packages/block-editor/src/utils/test/color-values.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,78 @@
/**
* Internal dependencies
*/
import { extractColorSlug } from '../color-values';
import { extractPresetSlug } from '../color-values';

describe( 'extractColorSlug', () => {
it( 'extracts the slug from the user preset format (var:preset|color|slug)', () => {
expect( extractColorSlug( 'var:preset|color|dark-text' ) ).toBe(
'dark-text'
);
describe( 'extractPresetSlug', () => {
it( 'extracts the slug for a color from the user preset format', () => {
expect(
extractPresetSlug( 'var:preset|color|dark-text', 'color' )
).toBe( 'dark-text' );
} );

it( 'extracts the slug from the theme CSS-var format (var(--wp--preset--color--slug))', () => {
it( 'extracts the slug for a color from the theme CSS-var format', () => {
expect(
extractColorSlug( 'var(--wp--preset--color--vivid-purple)' )
extractPresetSlug(
'var(--wp--preset--color--vivid-purple)',
'color'
)
).toBe( 'vivid-purple' );
} );

it( 'handles slugs that contain hyphens', () => {
it( 'extracts the slug for a gradient from the user preset format', () => {
expect(
extractColorSlug( 'var:preset|color|my-custom-blue-100' )
).toBe( 'my-custom-blue-100' );
extractPresetSlug(
'var:preset|gradient|blush-bordeaux',
'gradient'
)
).toBe( 'blush-bordeaux' );
} );

it( 'extracts the slug for a gradient from the theme CSS-var format', () => {
expect(
extractColorSlug( 'var(--wp--preset--color--my-custom-blue-100)' )
).toBe( 'my-custom-blue-100' );
extractPresetSlug(
'var(--wp--preset--gradient--blush-bordeaux)',
'gradient'
)
).toBe( 'blush-bordeaux' );
} );

it( 'returns undefined for a plain hex value', () => {
expect( extractColorSlug( '#000000' ) ).toBeUndefined();
it( 'handles slugs that contain hyphens for any type', () => {
expect(
extractPresetSlug(
'var:preset|gradient|my-custom-gradient-100',
'gradient'
)
).toBe( 'my-custom-gradient-100' );
expect(
extractPresetSlug(
'var(--wp--preset--gradient--my-custom-gradient-100)',
'gradient'
)
).toBe( 'my-custom-gradient-100' );
} );

it( 'returns undefined for a non-matching type', () => {
expect(
extractPresetSlug( 'var:preset|color|dark-text', 'gradient' )
).toBeUndefined();
expect(
extractPresetSlug(
'var(--wp--preset--color--vivid-purple)',
'gradient'
)
).toBeUndefined();
} );

it( 'returns undefined for non-string values', () => {
expect( extractColorSlug( undefined ) ).toBeUndefined();
expect( extractColorSlug( null ) ).toBeUndefined();
expect( extractColorSlug( 42 ) ).toBeUndefined();
expect( extractPresetSlug( undefined, 'gradient' ) ).toBeUndefined();
expect( extractPresetSlug( null, 'gradient' ) ).toBeUndefined();
expect( extractPresetSlug( 42, 'gradient' ) ).toBeUndefined();
} );

it( 'returns undefined for a theme var missing its closing parenthesis', () => {
expect(
extractColorSlug( 'var(--wp--preset--color--oops' )
extractPresetSlug( 'var(--wp--preset--gradient--oops', 'gradient' )
).toBeUndefined();
} );
} );
Loading