UNPKG

@wordpress/components

Version:
439 lines (386 loc) 11.2 kB
/** * External dependencies */ import { camelCase } from 'change-case'; import { Dimensions } from 'react-native'; /** * WordPress dependencies */ import { getPxFromCssUnit, useSetting, useMultipleOriginColorsAndGradients, } from '@wordpress/block-editor'; export const BLOCK_STYLE_ATTRIBUTES = [ 'textColor', 'backgroundColor', 'style', 'color', 'fontSize', ]; // Mapping style properties name to native. const BLOCK_STYLE_ATTRIBUTES_MAPPING = { textColor: 'color', text: 'color', background: 'backgroundColor', link: 'linkColor', placeholder: 'placeholderColor', }; const PADDING = 12; // $solid-border-space const UNKNOWN_VALUE = 'undefined'; const DEFAULT_FONT_SIZE = 16; export function getBlockPaddings( mergedStyle, wrapperPropsStyle, blockStyleAttributes, blockColors ) { const blockPaddings = {}; if ( ! mergedStyle.padding && ( wrapperPropsStyle?.backgroundColor || blockStyleAttributes?.backgroundColor || blockColors?.backgroundColor ) ) { blockPaddings.padding = PADDING; return blockPaddings; } // Prevent adding extra paddings to inner blocks without background colors. if ( mergedStyle?.padding && ! wrapperPropsStyle?.backgroundColor && ! blockStyleAttributes?.backgroundColor && ! blockColors?.backgroundColor ) { blockPaddings.padding = undefined; } return blockPaddings; } export function getBlockColors( blockStyleAttributes, defaultColors, blockName, baseGlobalStyles ) { const blockStyles = {}; const customBlockStyles = blockStyleAttributes?.style?.color || {}; const blockGlobalStyles = baseGlobalStyles?.blocks?.[ blockName ]; // Global styles colors. if ( blockGlobalStyles?.color ) { Object.entries( blockGlobalStyles.color ).forEach( ( [ key, value ] ) => { const styleKey = BLOCK_STYLE_ATTRIBUTES_MAPPING[ key ]; if ( styleKey && value !== UNKNOWN_VALUE ) { const color = customBlockStyles[ key ] ?? value; blockStyles[ styleKey ] = color; } } ); } else if ( baseGlobalStyles?.styles?.color?.text ) { blockStyles[ BLOCK_STYLE_ATTRIBUTES_MAPPING.text ] = baseGlobalStyles?.styles?.color?.text; } // Global styles elements. if ( blockGlobalStyles?.elements ) { const linkColor = blockGlobalStyles.elements?.link?.color?.text; const styleKey = BLOCK_STYLE_ATTRIBUTES_MAPPING.link; if ( styleKey && linkColor && linkColor !== UNKNOWN_VALUE ) { blockStyles[ styleKey ] = linkColor; } } // Custom colors. Object.entries( blockStyleAttributes ).forEach( ( [ key, value ] ) => { const isCustomColor = value?.startsWith?.( '#' ); let styleKey = key; if ( BLOCK_STYLE_ATTRIBUTES_MAPPING[ styleKey ] ) { styleKey = BLOCK_STYLE_ATTRIBUTES_MAPPING[ styleKey ]; } if ( ! isCustomColor ) { const mappedColor = Object.values( defaultColors ?? {} ).find( ( { slug } ) => slug === value ); if ( mappedColor ) { blockStyles[ styleKey ] = mappedColor.color; } } else { blockStyles[ styleKey ] = value; } } ); // Color placeholder. if ( blockStyles?.color ) { blockStyles[ BLOCK_STYLE_ATTRIBUTES_MAPPING.placeholder ] = blockStyles.color; } return blockStyles; } export function getBlockTypography( blockStyleAttributes, fontSizes, blockName, baseGlobalStyles ) { const typographyStyles = {}; const customBlockStyles = blockStyleAttributes?.style?.typography || {}; const blockGlobalStyles = baseGlobalStyles?.blocks?.[ blockName ]; const parsedFontSizes = Object.values( fontSizes ?? {} ); // Global styles. if ( blockGlobalStyles?.typography ) { const fontSize = blockGlobalStyles?.typography?.fontSize; const lineHeight = blockGlobalStyles?.typography?.lineHeight; if ( fontSize ) { if ( parseInt( fontSize, 10 ) ) { typographyStyles.fontSize = fontSize; } else { const mappedFontSize = parsedFontSizes.find( ( { slug } ) => slug === fontSize ); if ( mappedFontSize ) { typographyStyles.fontSize = mappedFontSize?.size; } } } if ( lineHeight ) { typographyStyles.lineHeight = lineHeight; } } if ( blockStyleAttributes?.fontSize && baseGlobalStyles ) { const mappedFontSize = parsedFontSizes.find( ( { slug } ) => slug === blockStyleAttributes?.fontSize ); if ( mappedFontSize ) { typographyStyles.fontSize = mappedFontSize?.size; } } // Custom styles. if ( customBlockStyles?.fontSize ) { typographyStyles.fontSize = customBlockStyles?.fontSize; } if ( customBlockStyles?.lineHeight ) { typographyStyles.lineHeight = customBlockStyles?.lineHeight; } return typographyStyles; } /** * Return a value from a certain path of the object. * Path is specified as an array of properties, like: [ 'parent', 'child' ]. * * @param {Object} object Input object. * @param {Array} path Path to the object property. * @return {*} Value of the object property at the specified path. */ const getValueFromObjectPath = ( object, path ) => { let value = object; path.forEach( ( fieldName ) => { value = value?.[ fieldName ]; } ); return value; }; export function parseStylesVariables( styles, mappedValues, customValues ) { let stylesBase = styles; const variables = [ 'preset', 'custom', 'var', 'fontSize' ]; if ( ! stylesBase ) { return styles; } variables.forEach( ( variable ) => { // Examples // var(--wp--preset--color--gray) // var(--wp--custom--body--typography--font-family) // var:preset|color|custom-color-2 const regex = new RegExp( `var\\(--wp--${ variable }--(.*?)\\)`, 'g' ); const varRegex = /\"var:preset\|color\|(.*?)\"/gm; const fontSizeRegex = /"fontSize":"(.*?)"/gm; if ( variable === 'preset' ) { stylesBase = stylesBase.replace( regex, ( _$1, $2 ) => { const path = $2.split( '--' ); const mappedPresetValue = mappedValues[ path[ 0 ] ]; if ( mappedPresetValue && mappedPresetValue.slug ) { const matchedValue = Object.values( mappedPresetValue.values ?? {} ).find( ( { slug } ) => slug === path[ 1 ] ); return matchedValue?.[ mappedPresetValue.slug ]; } return UNKNOWN_VALUE; } ); } if ( variable === 'custom' ) { const customValuesData = customValues ?? JSON.parse( stylesBase ); stylesBase = stylesBase.replace( regex, ( _$1, $2 ) => { const path = $2.split( '--' ); if ( path.reduce( ( prev, curr ) => prev && prev[ curr ], customValuesData ) ) { return getValueFromObjectPath( customValuesData, path ); } // Check for camelcase properties. return getValueFromObjectPath( customValuesData, [ ...path.slice( 0, path.length - 1 ), camelCase( path[ path.length - 1 ] ), ] ); } ); } if ( variable === 'var' ) { stylesBase = stylesBase.replace( varRegex, ( _$1, $2 ) => { if ( mappedValues?.color ) { const matchedValue = mappedValues.color?.values?.find( ( { slug } ) => slug === $2 ); return `"${ matchedValue?.color }"`; } return UNKNOWN_VALUE; } ); } if ( variable === 'fontSize' ) { const { width, height } = Dimensions.get( 'window' ); stylesBase = stylesBase.replace( fontSizeRegex, ( _$1, $2 ) => { const parsedFontSize = getPxFromCssUnit( $2, { width, height, fontSize: DEFAULT_FONT_SIZE, } ) || `${ DEFAULT_FONT_SIZE }px`; return `"fontSize":"${ parsedFontSize }"`; } ); } } ); return JSON.parse( stylesBase ); } export function getMappedValues( features, palette ) { const typography = features?.typography; const colors = [ ...( palette?.theme || [] ), ...( palette?.custom || [] ), ...( palette?.default || [] ), ]; const fontSizes = { ...typography?.fontSizes?.theme, ...typography?.fontSizes?.custom, }; const mappedValues = { color: { values: colors, slug: 'color', }, 'font-size': { values: fontSizes, slug: 'size', }, }; return mappedValues; } /** * Returns the normalized fontSizes to include the sizePx value for each of the different sizes. * * @param {Object} fontSizes found in global styles. * @return {Object} normalized sizes. */ function normalizeFontSizes( fontSizes ) { // Adds normalized PX values for each of the different keys. if ( ! fontSizes ) { return fontSizes; } const normalizedFontSizes = {}; const dimensions = Dimensions.get( 'window' ); [ 'default', 'theme', 'custom' ].forEach( ( key ) => { if ( fontSizes[ key ] ) { normalizedFontSizes[ key ] = fontSizes[ key ]?.map( ( fontSizeObject ) => { fontSizeObject.sizePx = getPxFromCssUnit( fontSizeObject.size, { width: dimensions.width, height: dimensions.height, fontSize: DEFAULT_FONT_SIZE, } ); return fontSizeObject; } ); } } ); return normalizedFontSizes; } export function useMobileGlobalStylesColors( type = 'colors' ) { const colorGradientSettings = useMultipleOriginColorsAndGradients(); const availableThemeColors = colorGradientSettings?.[ type ]?.reduce( ( colors, origin ) => colors.concat( origin?.[ type ] ), [] ); // Default editor colors/gradients if it's not a block-based theme. const colorPalette = type === 'colors' ? 'color.palette' : 'color.gradients'; const editorDefaultPalette = useSetting( colorPalette ); return availableThemeColors.length >= 1 ? availableThemeColors : editorDefaultPalette; } export function getColorsAndGradients( defaultEditorColors = [], defaultEditorGradients = [], rawFeatures ) { const features = rawFeatures ? JSON.parse( rawFeatures ) : {}; return { __experimentalGlobalStylesBaseStyles: null, __experimentalFeatures: { color: { ...( ! features?.color ? { text: true, background: true, palette: { default: defaultEditorColors, }, gradients: { default: defaultEditorGradients, }, } : features?.color ), defaultPalette: defaultEditorColors?.length > 0, defaultGradients: defaultEditorGradients?.length > 0, }, }, }; } export function getGlobalStyles( rawStyles, rawFeatures ) { const features = rawFeatures ? JSON.parse( rawFeatures ) : {}; const mappedValues = getMappedValues( features, features?.color?.palette ); const colors = parseStylesVariables( JSON.stringify( features?.color ), mappedValues ); const gradients = parseStylesVariables( JSON.stringify( features?.color?.gradients ), mappedValues ); const customValues = parseStylesVariables( JSON.stringify( features?.custom ), mappedValues ); const globalStyles = parseStylesVariables( rawStyles, mappedValues, customValues ); const fontSizes = normalizeFontSizes( features?.typography?.fontSizes ); return { __experimentalFeatures: { color: { palette: colors?.palette, gradients, text: features?.color?.text ?? true, background: features?.color?.background ?? true, defaultPalette: features?.color?.defaultPalette ?? true, defaultGradients: features?.color?.defaultGradients ?? true, }, typography: { fontSizes, customLineHeight: features?.custom?.[ 'line-height' ], }, spacing: features?.spacing, }, __experimentalGlobalStylesBaseStyles: globalStyles, }; }