@wordpress/block-editor
Version:
437 lines (419 loc) • 17.1 kB
JavaScript
/**
* 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