UNPKG

@wordpress/block-editor

Version:
437 lines (419 loc) 17.1 kB
/** * External dependencies */ import { camelCase } from 'change-case'; import { Dimensions } from 'react-native'; import { colord } from 'colord'; /** * WordPress dependencies */ import { usePreferredColorSchemeStyle } from '@wordpress/compose'; import { createContext, useContext } from '@wordpress/element'; import { getPxFromCssUnit } from '@wordpress/components'; /** * Internal dependencies */ import useMultipleOriginColorsAndGradients from '../colors-gradients/use-multiple-origin-colors-and-gradients'; import { useSettings } from '../use-settings'; import { SETTINGS_DEFAULTS } from '../../store/defaults'; const BLOCK_STYLE_ATTRIBUTES = ['textColor', 'backgroundColor', 'style', 'color', 'fontSize']; export const GlobalStylesContext = createContext({ style: {} }); GlobalStylesContext.BLOCK_STYLE_ATTRIBUTES = BLOCK_STYLE_ATTRIBUTES; // 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) { var _customBlockStyles$ke; const color = (_customBlockStyles$ke = customBlockStyles[key]) !== null && _customBlockStyles$ke !== void 0 ? _customBlockStyles$ke : 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 !== null && defaultColors !== void 0 ? 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; } function getBlockTypography(blockStyleAttributes, fontSizes, blockName, baseGlobalStyles) { const typographyStyles = {}; const customBlockStyles = blockStyleAttributes?.style?.typography || {}; const blockGlobalStyles = baseGlobalStyles?.blocks?.[blockName]; const parsedFontSizes = Object.values(fontSizes !== null && fontSizes !== void 0 ? 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) { var _mappedPresetValue$va; const matchedValue = Object.values((_mappedPresetValue$va = mappedPresetValue.values) !== null && _mappedPresetValue$va !== void 0 ? _mappedPresetValue$va : {}).find(({ slug }) => slug === path[1]); return matchedValue?.[mappedPresetValue.slug]; } return UNKNOWN_VALUE; }); } if (variable === 'custom') { const customValuesData = customValues !== null && customValues !== void 0 ? customValues : JSON.parse(stylesBase); stylesBase = stylesBase.replace(regex, (_$1, $2) => { const path = $2.split('--'); // Supports cases for variables like var(--wp--custom--color--background) if (path[0] === 'color') { const colorKey = path[path.length - 1]; if (mappedValues?.color) { const matchedValue = mappedValues.color?.values?.find(({ slug }) => slug === colorKey); if (matchedValue) { return `${matchedValue?.color}`; } } } 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); } 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) { if (!fontSizes) { return fontSizes; } const dimensions = Dimensions.get('window'); const normalizedFontSizes = {}; const keysToProcess = []; // Check if 'theme' or 'custom' keys exist and add them to keysToProcess array if (fontSizes?.theme) { keysToProcess.push('theme'); } if (fontSizes?.custom) { keysToProcess.push('custom'); } // If neither 'theme' nor 'custom' exist, add 'default' if it exists if (keysToProcess.length === 0 && fontSizes?.default) { keysToProcess.push('default'); } keysToProcess.forEach(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 defaultPaletteSetting = type === 'colors' ? 'color.palette' : 'color.gradients'; const [defaultPaletteValue] = useSettings(defaultPaletteSetting); // In edge cases, the default palette might be undefined. To avoid // exceptions across the editor in that case, we explicitly return // the default editor colors. const defaultPalette = defaultPaletteValue !== null && defaultPaletteValue !== void 0 ? defaultPaletteValue : SETTINGS_DEFAULTS.colors; return availableThemeColors.length >= 1 ? availableThemeColors : defaultPalette; } export function getColorsAndGradients(defaultEditorColors = [], defaultEditorGradients = [], rawFeatures) { const features = rawFeatures ? JSON.parse(rawFeatures) : {}; return { __experimentalGlobalStylesBaseStyles: null, __experimentalFeatures: { // Set an empty object to avoid errors from shared web components relying // upon block settings. E.g., the Gallery block. blocks: {}, 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) { var _features$color$text, _features$color$backg, _features$color$defau, _features$color$defau2; 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: { // Set an empty object to avoid errors from shared web components relying // upon block settings. E.g., the Gallery block. blocks: {}, color: { palette: colors?.palette, gradients, text: (_features$color$text = features?.color?.text) !== null && _features$color$text !== void 0 ? _features$color$text : true, background: (_features$color$backg = features?.color?.background) !== null && _features$color$backg !== void 0 ? _features$color$backg : true, defaultPalette: (_features$color$defau = features?.color?.defaultPalette) !== null && _features$color$defau !== void 0 ? _features$color$defau : true, defaultGradients: (_features$color$defau2 = features?.color?.defaultGradients) !== null && _features$color$defau2 !== void 0 ? _features$color$defau2 : true }, typography: { fontSizes, customLineHeight: features?.custom?.['line-height'] }, spacing: features?.spacing }, __experimentalGlobalStylesBaseStyles: globalStyles }; } export const getMergedGlobalStyles = (baseGlobalStyles, globalStyle, wrapperPropsStyle, blockAttributes, defaultColors, blockName, fontSizes) => { // Current support for general styles and blocks. const baseGlobalColors = { baseColors: { color: baseGlobalStyles?.color, typography: baseGlobalStyles?.typography, elements: { link: baseGlobalStyles?.elements?.link }, blocks: { 'core/button': baseGlobalStyles?.blocks?.['core/button'] } } }; const blockStyleAttributes = Object.fromEntries(Object.entries(blockAttributes !== null && blockAttributes !== void 0 ? blockAttributes : {}).filter(([key]) => BLOCK_STYLE_ATTRIBUTES.includes(key))); // This prevents certain wrapper styles from being applied to blocks that // don't support them yet. const wrapperPropsStyleFiltered = Object.fromEntries(Object.entries(wrapperPropsStyle !== null && wrapperPropsStyle !== void 0 ? wrapperPropsStyle : {}).filter(([key]) => BLOCK_STYLE_ATTRIBUTES.includes(key))); const mergedStyle = { ...baseGlobalColors, ...globalStyle, ...wrapperPropsStyleFiltered }; const blockColors = getBlockColors(blockStyleAttributes, defaultColors, blockName, baseGlobalStyles); const blockPaddings = getBlockPaddings(mergedStyle, wrapperPropsStyle, blockStyleAttributes, blockColors); const blockTypography = getBlockTypography(blockStyleAttributes, fontSizes, blockName, baseGlobalStyles); return { ...mergedStyle, ...blockPaddings, ...blockColors, ...blockTypography }; }; export const useGlobalStyles = () => { const globalStyles = useContext(GlobalStylesContext); return globalStyles; }; /** * Determine and apply appropriate color scheme based on global styles or device's light/dark mode. * * The function first attempts to retrieve the editor's background color from global styles. * If the detected background color is light, light styles are applied, and dark styles otherwise. * If no custom background color is defined, styles are applied using the device's dark/light setting. * * @param {Object} baseStyle - An object representing the base (light theme) styles for the editor. * @param {Object} darkStyle - An object representing the additional styles to apply when the editor is in dark mode. * * @return {Object} - The combined style object that should be applied to the editor. */ export const useEditorColorScheme = (baseStyle, darkStyle) => { const globalStyles = useGlobalStyles(); const deviceColorScheme = usePreferredColorSchemeStyle(baseStyle, darkStyle); const editorColors = globalStyles?.baseColors?.color; const editorBackgroundColor = editorColors?.background; const isBackgroundColorDefined = typeof editorBackgroundColor !== 'undefined' && editorBackgroundColor !== 'undefined'; if (isBackgroundColorDefined) { const isEditorBackgroundDark = colord(editorBackgroundColor).isDark(); return isEditorBackgroundDark ? { ...baseStyle, ...darkStyle } : baseStyle; } return deviceColorScheme; }; //# sourceMappingURL=use-global-styles-context.native.js.map