diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index 08ed251da48992..4be3382f854cd1 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -43,11 +43,11 @@ import { isBlobURL } from '@wordpress/blob'; /** * Internal dependencies */ -import { useToolsPanelDropdownMenuProps, getResolvedRefValue } from './utils'; +import { useToolsPanelDropdownMenuProps, getResolvedValue } from './utils'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; import { store as blockEditorStore } from '../../store'; -import { getResolvedThemeFilePath } from './theme-file-uri-utils'; + import { globalStylesDataKey, globalStylesLinksDataKey, @@ -680,7 +680,6 @@ export default function BackgroundPanel( { defaultControls = DEFAULT_CONTROLS, defaultValues = {}, headerLabel = __( 'Background image' ), - themeFileURIs, } ) { const { globalStyles, _links } = useSelect( ( select ) => { const { getSettings } = select( blockEditorStore ); @@ -691,8 +690,6 @@ export default function BackgroundPanel( { }; }, [] ); - themeFileURIs = themeFileURIs || _links?.[ 'wp:theme-file' ]; - /* * Resolve any inherited "ref" pointers. * Should the block editor need inherited values @@ -709,26 +706,17 @@ export default function BackgroundPanel( { Object.entries( inheritedValue?.background ).forEach( ( [ key, backgroundValue ] ) => { - resolvedValues.background[ key ] = getResolvedRefValue( + resolvedValues.background[ key ] = getResolvedValue( backgroundValue, { styles: globalStyles, + _links, } ); - if ( - 'backgroundImage' === key && - resolvedValues.background[ key ]?.url - ) { - resolvedValues.background[ key ].url = - getResolvedThemeFilePath( - resolvedValues.background[ key ].url, - themeFileURIs - ); - } } ); return resolvedValues; - }, [ globalStyles, inheritedValue ] ); + }, [ globalStyles, _links, inheritedValue ] ); const resetAllFilter = useCallback( ( previousValue ) => { return { diff --git a/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js b/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js deleted file mode 100644 index 06c482b67826bd..00000000000000 --- a/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Internal dependencies - */ -import { - setThemeFileUris, - getResolvedThemeFilePath, -} from '../theme-file-uri-utils'; - -const themeFileURIs = [ - { - name: 'file:./assets/image.jpg', - href: 'https://wordpress.org/assets/image.jpg', - target: 'styles.background.backgroundImage.url', - }, - { - name: 'file:./assets/other/image.jpg', - href: 'https://wordpress.org/assets/other/image.jpg', - target: "styles.blocks.['core/group].background.backgroundImage.url", - }, -]; - -describe( 'setThemeFileUris()', () => { - const themeJson = { - styles: { - background: { - backgroundImage: { - url: 'file:./assets/image.jpg', - }, - }, - }, - }; - - it( 'should replace relative paths with resolved URIs if found in themeFileURIs', () => { - const newThemeJson = setThemeFileUris( themeJson, themeFileURIs ); - expect( - newThemeJson.styles.background.backgroundImage.url === - 'https://wordpress.org/assets/image.jpg' - ).toBe( true ); - // Object reference should be the same as the function is mutating the object. - expect( newThemeJson ).toEqual( themeJson ); - } ); -} ); - -describe( 'getResolvedThemeFilePath()', () => { - it.each( [ - [ - 'file:./assets/image.jpg', - 'https://wordpress.org/assets/image.jpg', - 'Should return absolute URL if found in themeFileURIs', - ], - [ - 'file:./misc/image.jpg', - 'file:./misc/image.jpg', - 'Should return value if not found in themeFileURIs', - ], - [ - 'https://wordpress.org/assets/image.jpg', - 'https://wordpress.org/assets/image.jpg', - 'Should not match absolute URLs', - ], - ] )( 'Given file %s and return value %s: %s', ( file, returnedValue ) => { - expect( - getResolvedThemeFilePath( file, themeFileURIs ) === returnedValue - ).toBe( true ); - } ); -} ); diff --git a/packages/block-editor/src/components/global-styles/test/utils.js b/packages/block-editor/src/components/global-styles/test/utils.js index f06a09494ab9fb..0ab13e9f92134d 100644 --- a/packages/block-editor/src/components/global-styles/test/utils.js +++ b/packages/block-editor/src/components/global-styles/test/utils.js @@ -7,6 +7,7 @@ import { getPresetVariableFromValue, getValueFromVariable, scopeFeatureSelectors, + getResolvedThemeFilePath, } from '../utils'; describe( 'editor utils', () => { @@ -52,6 +53,20 @@ describe( 'editor utils', () => { }, }, }, + _links: { + 'wp:theme-file': [ + { + name: 'file:./assets/image.jpg', + href: 'https://wordpress.org/assets/image.jpg', + target: 'styles.background.backgroundImage.url', + }, + { + name: 'file:./assets/other/image.jpg', + href: 'https://wordpress.org/assets/other/image.jpg', + target: "styles.blocks.['core/group].background.backgroundImage.url", + }, + ], + }, isGlobalStylesUserThemeJSON: true, }; @@ -366,4 +381,34 @@ describe( 'editor utils', () => { } ); } ); } ); + + describe( 'getResolvedThemeFilePath()', () => { + it.each( [ + [ + 'file:./assets/image.jpg', + 'https://wordpress.org/assets/image.jpg', + 'Should return absolute URL if found in themeFileURIs', + ], + [ + 'file:./misc/image.jpg', + 'file:./misc/image.jpg', + 'Should return value if not found in themeFileURIs', + ], + [ + 'https://wordpress.org/assets/image.jpg', + 'https://wordpress.org/assets/image.jpg', + 'Should not match absolute URLs', + ], + ] )( + 'Given file %s and return value %s: %s', + ( file, returnedValue ) => { + expect( + getResolvedThemeFilePath( + file, + themeJson._links[ 'wp:theme-file' ] + ) === returnedValue + ).toBe( true ); + } + ); + } ); } ); diff --git a/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js b/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js deleted file mode 100644 index 1ab05a45f0d54b..00000000000000 --- a/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Internal dependencies - */ -import { getValueFromObjectPath } from '../../utils/object'; - -/** - * Looks up a theme file URI based on a relative path. - * - * @param {string} file A relative path. - * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. - * @return {string?} A resolved theme file URI, if one is found in the themeFileURIs collection. - */ -export function getResolvedThemeFilePath( file, themeFileURIs = [] ) { - const uri = themeFileURIs.find( - ( themeFileUri ) => themeFileUri.name === file - ); - - if ( ! uri?.href ) { - return file; - } - - return uri?.href; -} - -/** - * Mutates an object by settings a value at the provided path. - * - * @param {Object} object Object to set a value in. - * @param {number|string|Array} path Path in the object to modify. - * @param {*} value New value to set. - * @return {Object} Object with the new value set. - */ -function setMutably( object, path, value ) { - path = path.split( '.' ); - const finalValueKey = path.pop(); - let prev = object; - - for ( const key of path ) { - const current = prev[ key ]; - prev = current; - } - - prev[ finalValueKey ] = value; - - return object; -} - -/** - * Resolves any relative paths if a corresponding theme file URI is available. - * Note: this function mutates the object and is specifically to be used in - * an async styles build context in useGlobalStylesOutput - * - * @param {Object} themeJson Theme.json/Global styles tree. - * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. - * @return {Object} Returns mutated object. - */ -export function setThemeFileUris( themeJson, themeFileURIs ) { - if ( ! themeJson?.styles || ! themeFileURIs ) { - return themeJson; - } - - themeFileURIs.forEach( ( { name, href, target } ) => { - const value = getValueFromObjectPath( themeJson, target ); - if ( value === name ) { - /* - * The object must not be updated immutably here because the - * themeJson is a reference to the global styles tree used as a dependency in the - * useGlobalStylesOutputWithConfig() hook. If we don't mutate the object, - * the hook will detect the change and re-render the component, resulting - * in a maximum depth exceeded error. - */ - themeJson = setMutably( themeJson, target, href ); - } - } ); - - return themeJson; -} diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 2f06a8d63acfa1..4c81184c832fc8 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -25,7 +25,7 @@ import { appendToSelector, getBlockStyleVariationSelector, compileStyleValue, - getResolvedRefValue, + getResolvedValue, } from './utils'; import { getBlockCSSSelector } from './get-block-css-selector'; import { getTypographyFontSizeValue } from './typography-utils'; @@ -38,7 +38,6 @@ import { store as blockEditorStore } from '../../store'; import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; import { unlock } from '../../lock-unlock'; -import { setThemeFileUris } from './theme-file-uri-utils'; // Elements that rely on class names in their selectors. const ELEMENT_CLASS_NAMES = { @@ -394,12 +393,10 @@ export function getStylesDeclarations( * Resolve dynamic values before they are compiled by the style engine, * which doesn't (yet) resolve dynamic values. */ - if ( blockStyles.background?.backgroundImage?.ref ) { - const refPath = - blockStyles.background.backgroundImage.ref.split( '.' ); - blockStyles.background.backgroundImage = getValueFromObjectPath( - tree, - refPath + if ( blockStyles.background?.backgroundImage ) { + blockStyles.background.backgroundImage = getResolvedValue( + blockStyles.background.backgroundImage, + tree ); } @@ -432,7 +429,7 @@ export function getStylesDeclarations( ? rule.key : kebabCase( rule.key ); - let ruleValue = getResolvedRefValue( rule.value, tree, undefined ); + let ruleValue = getResolvedValue( rule.value, tree ); if ( ! ruleValue ) { return; @@ -1399,10 +1396,6 @@ export function useGlobalStylesOutputWithConfig( disableRootPadding ) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); - mergedConfig = setThemeFileUris( - mergedConfig, - mergedConfig?._links?.[ 'wp:theme-file' ] - ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. const disableLayoutStyles = useSelect( ( select ) => { diff --git a/packages/block-editor/src/components/global-styles/utils.js b/packages/block-editor/src/components/global-styles/utils.js index e9fb5608ec3d41..e05720c75b48ec 100644 --- a/packages/block-editor/src/components/global-styles/utils.js +++ b/packages/block-editor/src/components/global-styles/utils.js @@ -554,27 +554,58 @@ export function compileStyleValue( uncompiledValue ) { } /** - * Resolves ref values in theme JSON. + * Looks up a theme file URI based on a relative path. * - * @param {Object|string} ruleValue A block style value that may contain a reference to a theme.json value. - * @param {Object} tree A theme.json object. - * @param {*} defaultValue Optional default value to return if the resolved value is nullish. - * @return {*|string} The resolved value, the defaultValue or incoming ruleValue. + * @param {string} file A relative path. + * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. + * @return {string} A resolved theme file URI, if one is found in the themeFileURIs collection. */ -export function getResolvedRefValue( ruleValue, tree, defaultValue ) { +export function getResolvedThemeFilePath( file, themeFileURIs ) { + if ( ! file || ! themeFileURIs || ! Array.isArray( themeFileURIs ) ) { + return file; + } + + const uri = themeFileURIs.find( + ( themeFileUri ) => themeFileUri?.name === file + ); + + if ( ! uri?.href ) { + return file; + } + + return uri?.href; +} + +/** + * Resolves ref and relative path values in theme JSON. + * + * @param {Object|string} ruleValue A block style value that may contain a reference to a theme.json value. + * @param {Object} tree A theme.json object. + * @return {*} The resolved value or incoming ruleValue. + */ +export function getResolvedValue( ruleValue, tree ) { + let resolvedValue = ruleValue; if ( typeof ruleValue !== 'string' && ruleValue?.ref ) { const refPath = ruleValue.ref.split( '.' ); - const resolvedValue = compileStyleValue( - getValueFromObjectPath( tree, refPath, defaultValue ) + resolvedValue = compileStyleValue( + getValueFromObjectPath( tree, refPath ) ); /* * Presence of another ref indicates a reference to another dynamic value. * Pointing to another dynamic value is not supported. */ if ( ! resolvedValue || resolvedValue?.ref ) { - return defaultValue; + return; } - return resolvedValue; } - return ruleValue; + + // Resolve relative paths. + if ( resolvedValue?.url ) { + resolvedValue.url = getResolvedThemeFilePath( + resolvedValue.url, + tree?._links?.[ 'wp:theme-file' ] + ); + } + + return resolvedValue; }