@wordpress/block-editor
Version:
1,005 lines (834 loc) • 36.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getBlockSelectors = void 0;
exports.getLayoutStyles = getLayoutStyles;
exports.getNodesWithStyles = exports.getNodesWithSettings = void 0;
exports.getStylesDeclarations = getStylesDeclarations;
exports.toStyles = exports.toCustomProperties = void 0;
exports.toSvgFilters = toSvgFilters;
exports.useGlobalStylesOutput = useGlobalStylesOutput;
exports.useGlobalStylesOutputWithConfig = useGlobalStylesOutputWithConfig;
var _element = require("@wordpress/element");
var _lodash = require("lodash");
var _blocks = require("@wordpress/blocks");
var _data = require("@wordpress/data");
var _styleEngine = require("@wordpress/style-engine");
var _utils = require("./utils");
var _getBlockCssSelector = require("./get-block-css-selector");
var _typographyUtils = require("./typography-utils");
var _context = require("./context");
var _hooks = require("./hooks");
var _components = require("../duotone/components");
var _gap = require("../../hooks/gap");
var _store = require("../../store");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
// List of block support features that can have their related styles
// generated under their own feature level selector rather than the block's.
const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
__experimentalBorder: 'border',
color: 'color',
spacing: 'spacing',
typography: 'typography'
};
function compileStyleValue(uncompiledValue) {
const VARIABLE_REFERENCE_PREFIX = 'var:';
const VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE = '|';
const VARIABLE_PATH_SEPARATOR_TOKEN_STYLE = '--';
if (uncompiledValue?.startsWith?.(VARIABLE_REFERENCE_PREFIX)) {
const variable = uncompiledValue.slice(VARIABLE_REFERENCE_PREFIX.length).split(VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE).join(VARIABLE_PATH_SEPARATOR_TOKEN_STYLE);
return `var(--wp--${variable})`;
}
return uncompiledValue;
}
/**
* Transform given preset tree into a set of style declarations.
*
* @param {Object} blockPresets
* @param {Object} mergedSettings Merged theme.json settings.
*
* @return {Array<Object>} An array of style declarations.
*/
function getPresetsDeclarations(blockPresets = {}, mergedSettings) {
return _utils.PRESET_METADATA.reduce((declarations, {
path,
valueKey,
valueFunc,
cssVarInfix
}) => {
const presetByOrigin = (0, _lodash.get)(blockPresets, path, []);
['default', 'theme', 'custom'].forEach(origin => {
if (presetByOrigin[origin]) {
presetByOrigin[origin].forEach(value => {
if (valueKey && !valueFunc) {
declarations.push(`--wp--preset--${cssVarInfix}--${(0, _lodash.kebabCase)(value.slug)}: ${value[valueKey]}`);
} else if (valueFunc && typeof valueFunc === 'function') {
declarations.push(`--wp--preset--${cssVarInfix}--${(0, _lodash.kebabCase)(value.slug)}: ${valueFunc(value, mergedSettings)}`);
}
});
}
});
return declarations;
}, []);
}
/**
* Transform given preset tree into a set of preset class declarations.
*
* @param {?string} blockSelector
* @param {Object} blockPresets
* @return {string} CSS declarations for the preset classes.
*/
function getPresetsClasses(blockSelector = '*', blockPresets = {}) {
return _utils.PRESET_METADATA.reduce((declarations, {
path,
cssVarInfix,
classes
}) => {
if (!classes) {
return declarations;
}
const presetByOrigin = (0, _lodash.get)(blockPresets, path, []);
['default', 'theme', 'custom'].forEach(origin => {
if (presetByOrigin[origin]) {
presetByOrigin[origin].forEach(({
slug
}) => {
classes.forEach(({
classSuffix,
propertyName
}) => {
const classSelectorToUse = `.has-${(0, _lodash.kebabCase)(slug)}-${classSuffix}`;
const selectorToUse = blockSelector.split(',') // Selector can be "h1, h2, h3"
.map(selector => `${selector}${classSelectorToUse}`).join(',');
const value = `var(--wp--preset--${cssVarInfix}--${(0, _lodash.kebabCase)(slug)})`;
declarations += `${selectorToUse}{${propertyName}: ${value} !important;}`;
});
});
}
});
return declarations;
}, '');
}
function getPresetsSvgFilters(blockPresets = {}) {
return _utils.PRESET_METADATA.filter( // Duotone are the only type of filters for now.
metadata => metadata.path.at(-1) === 'duotone').flatMap(metadata => {
const presetByOrigin = (0, _lodash.get)(blockPresets, metadata.path, {});
return ['default', 'theme'].filter(origin => presetByOrigin[origin]).flatMap(origin => presetByOrigin[origin].map(preset => (0, _element.renderToString)((0, _element.createElement)(_components.PresetDuotoneFilter, {
preset: preset,
key: preset.slug
})))).join('');
});
}
function flattenTree(input = {}, prefix, token) {
let result = [];
Object.keys(input).forEach(key => {
const newKey = prefix + (0, _lodash.kebabCase)(key.replace('/', '-'));
const newLeaf = input[key];
if (newLeaf instanceof Object) {
const newPrefix = newKey + token;
result = [...result, ...flattenTree(newLeaf, newPrefix, token)];
} else {
result.push(`${newKey}: ${newLeaf}`);
}
});
return result;
}
/**
* Gets variation selector string from feature selector.
*
* @param {string} featureSelector The feature selector.
*
* @param {string} styleVariationSelector The style variation selector.
* @return {string} Combined selector string.
*
*/
function concatFeatureVariationSelectorString(featureSelector, styleVariationSelector) {
const featureSelectors = featureSelector.split(',');
const combinedSelectors = [];
featureSelectors.forEach(selector => {
combinedSelectors.push(`${styleVariationSelector.trim()}${selector.trim()}`);
});
return combinedSelectors.join(', ');
}
/**
* Generate style declarations for a block's custom feature and subfeature
* selectors.
*
* NOTE: The passed `styles` object will be mutated by this function.
*
* @param {Object} selectors Custom selectors object for a block.
* @param {Object} styles A block's styles object.
*
* @return {Object} Style declarations.
*/
const getFeatureDeclarations = (selectors, styles) => {
const declarations = {};
Object.entries(selectors).forEach(([feature, selector]) => {
// We're only processing features/subfeatures that have styles.
if (feature === 'root' || !styles?.[feature]) {
return;
}
const isShorthand = typeof selector === 'string'; // If we have a selector object instead of shorthand process it.
if (!isShorthand) {
Object.entries(selector).forEach(([subfeature, subfeatureSelector]) => {
// Don't process root feature selector yet or any
// subfeature that doesn't have a style.
if (subfeature === 'root' || !styles?.[feature][subfeature]) {
return;
} // Create a temporary styles object and build
// declarations for subfeature.
const subfeatureStyles = {
[feature]: {
[subfeature]: styles[feature][subfeature]
}
};
const newDeclarations = getStylesDeclarations(subfeatureStyles); // Merge new declarations in with any others that
// share the same selector.
declarations[subfeatureSelector] = [...(declarations[subfeatureSelector] || []), ...newDeclarations]; // Remove the subfeature's style now it will be
// included under its own selector not the block's.
delete styles[feature][subfeature];
});
} // Now subfeatures have been processed and removed, we can
// process root, or shorthand, feature selectors.
if (isShorthand || selector.root) {
const featureSelector = isShorthand ? selector : selector.root; // Create temporary style object and build declarations for feature.
const featureStyles = {
[feature]: styles[feature]
};
const newDeclarations = getStylesDeclarations(featureStyles); // Merge new declarations with any others that share the selector.
declarations[featureSelector] = [...(declarations[featureSelector] || []), ...newDeclarations]; // Remove the feature from the block's styles now as it will be
// included under its own selector not the block's.
delete styles[feature];
}
});
return declarations;
};
/**
* Transform given style tree into a set of style declarations.
*
* @param {Object} blockStyles Block styles.
*
* @param {string} selector The selector these declarations should attach to.
*
* @param {boolean} useRootPaddingAlign Whether to use CSS custom properties in root selector.
*
* @param {Object} tree A theme.json tree containing layout definitions.
*
* @return {Array} An array of style declarations.
*/
function getStylesDeclarations(blockStyles = {}, selector = '', useRootPaddingAlign, tree = {}) {
const isRoot = _utils.ROOT_BLOCK_SELECTOR === selector;
const output = Object.entries(_blocks.__EXPERIMENTAL_STYLE_PROPERTY).reduce((declarations, [key, {
value,
properties,
useEngine,
rootOnly
}]) => {
if (rootOnly && !isRoot) {
return declarations;
}
const pathToValue = value;
if (pathToValue[0] === 'elements' || useEngine) {
return declarations;
}
const styleValue = (0, _lodash.get)(blockStyles, pathToValue); // Root-level padding styles don't currently support strings with CSS shorthand values.
// This may change: https://github.com/WordPress/gutenberg/issues/40132.
if (key === '--wp--style--root--padding' && (typeof styleValue === 'string' || !useRootPaddingAlign)) {
return declarations;
}
if (properties && typeof styleValue !== 'string') {
Object.entries(properties).forEach(entry => {
const [name, prop] = entry;
if (!(0, _lodash.get)(styleValue, [prop], false)) {
// Do not create a declaration
// for sub-properties that don't have any value.
return;
}
const cssProperty = name.startsWith('--') ? name : (0, _lodash.kebabCase)(name);
declarations.push(`${cssProperty}: ${compileStyleValue((0, _lodash.get)(styleValue, [prop]))}`);
});
} else if ((0, _lodash.get)(blockStyles, pathToValue, false)) {
const cssProperty = key.startsWith('--') ? key : (0, _lodash.kebabCase)(key);
declarations.push(`${cssProperty}: ${compileStyleValue((0, _lodash.get)(blockStyles, pathToValue))}`);
}
return declarations;
}, []); // The goal is to move everything to server side generated engine styles
// This is temporary as we absorb more and more styles into the engine.
const extraRules = (0, _styleEngine.getCSSRules)(blockStyles);
extraRules.forEach(rule => {
// Don't output padding properties if padding variables are set.
if (isRoot && useRootPaddingAlign && rule.key.startsWith('padding')) {
return;
}
const cssProperty = rule.key.startsWith('--') ? rule.key : (0, _lodash.kebabCase)(rule.key);
let ruleValue = rule.value;
if (typeof ruleValue !== 'string' && ruleValue?.ref) {
const refPath = ruleValue.ref.split('.');
ruleValue = (0, _lodash.get)(tree, refPath); // Presence of another ref indicates a reference to another dynamic value.
// Pointing to another dynamic value is not supported.
if (!ruleValue || ruleValue?.ref) {
return;
}
} // Calculate fluid typography rules where available.
if (cssProperty === 'font-size') {
/*
* getTypographyFontSizeValue() will check
* if fluid typography has been activated and also
* whether the incoming value can be converted to a fluid value.
* Values that already have a "clamp()" function will not pass the test,
* and therefore the original $value will be returned.
*/
ruleValue = (0, _typographyUtils.getTypographyFontSizeValue)({
size: ruleValue
}, (0, _typographyUtils.getFluidTypographyOptionsFromSettings)(tree?.settings));
}
output.push(`${cssProperty}: ${ruleValue}`);
});
return output;
}
/**
* Get generated CSS for layout styles by looking up layout definitions provided
* in theme.json, and outputting common layout styles, and specific blockGap values.
*
* @param {Object} props
* @param {Object} props.tree A theme.json tree containing layout definitions.
* @param {Object} props.style A style object containing spacing values.
* @param {string} props.selector Selector used to group together layout styling rules.
* @param {boolean} props.hasBlockGapSupport Whether or not the theme opts-in to blockGap support.
* @param {boolean} props.hasFallbackGapSupport Whether or not the theme allows fallback gap styles.
* @param {?string} props.fallbackGapValue An optional fallback gap value if no real gap value is available.
* @return {string} Generated CSS rules for the layout styles.
*/
function getLayoutStyles({
tree,
style,
selector,
hasBlockGapSupport,
hasFallbackGapSupport,
fallbackGapValue
}) {
let ruleset = '';
let gapValue = hasBlockGapSupport ? (0, _gap.getGapCSSValue)(style?.spacing?.blockGap) : ''; // Ensure a fallback gap value for the root layout definitions,
// and use a fallback value if one is provided for the current block.
if (hasFallbackGapSupport) {
if (selector === _utils.ROOT_BLOCK_SELECTOR) {
gapValue = !gapValue ? '0.5em' : gapValue;
} else if (!hasBlockGapSupport && fallbackGapValue) {
gapValue = fallbackGapValue;
}
}
if (gapValue && tree?.settings?.layout?.definitions) {
Object.values(tree.settings.layout.definitions).forEach(({
className,
name,
spacingStyles
}) => {
// Allow outputting fallback gap styles for flex layout type when block gap support isn't available.
if (!hasBlockGapSupport && 'flex' !== name && 'grid' !== name) {
return;
}
if (spacingStyles?.length) {
spacingStyles.forEach(spacingStyle => {
const declarations = [];
if (spacingStyle.rules) {
Object.entries(spacingStyle.rules).forEach(([cssProperty, cssValue]) => {
declarations.push(`${cssProperty}: ${cssValue ? cssValue : gapValue}`);
});
}
if (declarations.length) {
let combinedSelector = '';
if (!hasBlockGapSupport) {
// For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles.
combinedSelector = selector === _utils.ROOT_BLOCK_SELECTOR ? `:where(.${className}${spacingStyle?.selector || ''})` : `:where(${selector}.${className}${spacingStyle?.selector || ''})`;
} else {
combinedSelector = selector === _utils.ROOT_BLOCK_SELECTOR ? `:where(${selector} .${className})${spacingStyle?.selector || ''}` : `${selector}-${className}${spacingStyle?.selector || ''}`;
}
ruleset += `${combinedSelector} { ${declarations.join('; ')}; }`;
}
});
}
}); // For backwards compatibility, ensure the legacy block gap CSS variable is still available.
if (selector === _utils.ROOT_BLOCK_SELECTOR && hasBlockGapSupport) {
ruleset += `${selector} { --wp--style--block-gap: ${gapValue}; }`;
}
} // Output base styles
if (selector === _utils.ROOT_BLOCK_SELECTOR && tree?.settings?.layout?.definitions) {
const validDisplayModes = ['block', 'flex', 'grid'];
Object.values(tree.settings.layout.definitions).forEach(({
className,
displayMode,
baseStyles
}) => {
if (displayMode && validDisplayModes.includes(displayMode)) {
ruleset += `${selector} .${className} { display:${displayMode}; }`;
}
if (baseStyles?.length) {
baseStyles.forEach(baseStyle => {
const declarations = [];
if (baseStyle.rules) {
Object.entries(baseStyle.rules).forEach(([cssProperty, cssValue]) => {
declarations.push(`${cssProperty}: ${cssValue}`);
});
}
if (declarations.length) {
const combinedSelector = `${selector} .${className}${baseStyle?.selector || ''}`;
ruleset += `${combinedSelector} { ${declarations.join('; ')}; }`;
}
});
}
});
}
return ruleset;
}
const STYLE_KEYS = ['border', 'color', 'dimensions', 'spacing', 'typography', 'filter', 'outline', 'shadow'];
function pickStyleKeys(treeToPickFrom) {
if (!treeToPickFrom) {
return {};
}
const entries = Object.entries(treeToPickFrom);
const pickedEntries = entries.filter(([key]) => STYLE_KEYS.includes(key)); // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
const clonedEntries = pickedEntries.map(([key, style]) => [key, JSON.parse(JSON.stringify(style))]);
return Object.fromEntries(clonedEntries);
}
const getNodesWithStyles = (tree, blockSelectors) => {
var _tree$styles$blocks;
const nodes = [];
if (!tree?.styles) {
return nodes;
} // Top-level.
const styles = pickStyleKeys(tree.styles);
if (styles) {
nodes.push({
styles,
selector: _utils.ROOT_BLOCK_SELECTOR
});
}
Object.entries(_blocks.__EXPERIMENTAL_ELEMENTS).forEach(([name, selector]) => {
if (tree.styles?.elements?.[name]) {
nodes.push({
styles: tree.styles?.elements?.[name],
selector
});
}
}); // Iterate over blocks: they can have styles & elements.
Object.entries((_tree$styles$blocks = tree.styles?.blocks) !== null && _tree$styles$blocks !== void 0 ? _tree$styles$blocks : {}).forEach(([blockName, node]) => {
var _node$elements;
const blockStyles = pickStyleKeys(node);
if (node?.variations) {
const variations = {};
Object.keys(node.variations).forEach(variation => {
variations[variation] = pickStyleKeys(node.variations[variation]);
});
blockStyles.variations = variations;
}
if (blockStyles && blockSelectors?.[blockName]?.selector) {
nodes.push({
duotoneSelector: blockSelectors[blockName].duotoneSelector,
fallbackGapValue: blockSelectors[blockName].fallbackGapValue,
hasLayoutSupport: blockSelectors[blockName].hasLayoutSupport,
selector: blockSelectors[blockName].selector,
styles: blockStyles,
featureSelectors: blockSelectors[blockName].featureSelectors,
styleVariationSelectors: blockSelectors[blockName].styleVariationSelectors
});
}
Object.entries((_node$elements = node?.elements) !== null && _node$elements !== void 0 ? _node$elements : {}).forEach(([elementName, value]) => {
if (value && blockSelectors?.[blockName] && _blocks.__EXPERIMENTAL_ELEMENTS[elementName]) {
nodes.push({
styles: value,
selector: blockSelectors[blockName]?.selector.split(',').map(sel => {
const elementSelectors = _blocks.__EXPERIMENTAL_ELEMENTS[elementName].split(',');
return elementSelectors.map(elementSelector => sel + ' ' + elementSelector);
}).join(',')
});
}
});
});
return nodes;
};
exports.getNodesWithStyles = getNodesWithStyles;
const getNodesWithSettings = (tree, blockSelectors) => {
var _tree$settings$blocks;
const nodes = [];
if (!tree?.settings) {
return nodes;
}
const pickPresets = treeToPickFrom => {
const presets = {};
_utils.PRESET_METADATA.forEach(({
path
}) => {
const value = (0, _lodash.get)(treeToPickFrom, path, false);
if (value !== false) {
(0, _lodash.set)(presets, path, value);
}
});
return presets;
}; // Top-level.
const presets = pickPresets(tree.settings);
const custom = tree.settings?.custom;
if (Object.keys(presets).length > 0 || custom) {
nodes.push({
presets,
custom,
selector: _utils.ROOT_BLOCK_SELECTOR
});
} // Blocks.
Object.entries((_tree$settings$blocks = tree.settings?.blocks) !== null && _tree$settings$blocks !== void 0 ? _tree$settings$blocks : {}).forEach(([blockName, node]) => {
const blockPresets = pickPresets(node);
const blockCustom = node.custom;
if (Object.keys(blockPresets).length > 0 || blockCustom) {
nodes.push({
presets: blockPresets,
custom: blockCustom,
selector: blockSelectors[blockName]?.selector
});
}
});
return nodes;
};
exports.getNodesWithSettings = getNodesWithSettings;
const toCustomProperties = (tree, blockSelectors) => {
const settings = getNodesWithSettings(tree, blockSelectors);
let ruleset = '';
settings.forEach(({
presets,
custom,
selector
}) => {
const declarations = getPresetsDeclarations(presets, tree?.settings);
const customProps = flattenTree(custom, '--wp--custom--', '--');
if (customProps.length > 0) {
declarations.push(...customProps);
}
if (declarations.length > 0) {
ruleset += `${selector}{${declarations.join(';')};}`;
}
});
return ruleset;
};
exports.toCustomProperties = toCustomProperties;
const toStyles = (tree, blockSelectors, hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles = false) => {
const nodesWithStyles = getNodesWithStyles(tree, blockSelectors);
const nodesWithSettings = getNodesWithSettings(tree, blockSelectors);
const useRootPaddingAlign = tree?.settings?.useRootPaddingAwareAlignments;
const {
contentSize,
wideSize
} = tree?.settings?.layout || {};
/*
* Reset default browser margin on the root body element.
* This is set on the root selector **before** generating the ruleset
* from the `theme.json`. This is to ensure that if the `theme.json` declares
* `margin` in its `spacing` declaration for the `body` element then these
* user-generated values take precedence in the CSS cascade.
* @link https://github.com/WordPress/gutenberg/issues/36147.
*/
let ruleset = 'body {margin: 0;';
if (contentSize) {
ruleset += ` --wp--style--global--content-size: ${contentSize};`;
}
if (wideSize) {
ruleset += ` --wp--style--global--wide-size: ${wideSize};`;
}
if (useRootPaddingAlign) {
/*
* These rules reproduce the ones from https://github.com/WordPress/gutenberg/blob/79103f124925d1f457f627e154f52a56228ed5ad/lib/class-wp-theme-json-gutenberg.php#L2508
* almost exactly, but for the selectors that target block wrappers in the front end. This code only runs in the editor, so it doesn't need those selectors.
*/
ruleset += `padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) }
.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }
.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }
.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }
.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }
.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }
.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where(.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0;`;
}
ruleset += '}';
nodesWithStyles.forEach(({
selector,
duotoneSelector,
styles,
fallbackGapValue,
hasLayoutSupport,
featureSelectors,
styleVariationSelectors
}) => {
// Process styles for block support features with custom feature level
// CSS selectors set.
if (featureSelectors) {
const featureDeclarations = getFeatureDeclarations(featureSelectors, styles);
Object.entries(featureDeclarations).forEach(([cssSelector, declarations]) => {
if (declarations.length) {
const rules = declarations.join(';');
ruleset += `${cssSelector}{${rules};}`;
}
});
}
if (styleVariationSelectors) {
Object.entries(styleVariationSelectors).forEach(([styleVariationName, styleVariationSelector]) => {
const styleVariations = styles?.variations?.[styleVariationName];
if (styleVariations) {
// If the block uses any custom selectors for block support, add those first.
if (featureSelectors) {
const featureDeclarations = getFeatureDeclarations(featureSelectors, styleVariations);
Object.entries(featureDeclarations).forEach(([baseSelector, declarations]) => {
if (declarations.length) {
const cssSelector = concatFeatureVariationSelectorString(baseSelector, styleVariationSelector);
const rules = declarations.join(';');
ruleset += `${cssSelector}{${rules};}`;
}
});
} // Otherwise add regular selectors.
const styleVariationDeclarations = getStylesDeclarations(styleVariations, styleVariationSelector, useRootPaddingAlign, tree);
if (styleVariationDeclarations.length) {
ruleset += `${styleVariationSelector}{${styleVariationDeclarations.join(';')};}`;
}
}
});
} // Process duotone styles.
if (duotoneSelector) {
const duotoneStyles = {};
if (styles?.filter) {
duotoneStyles.filter = styles.filter;
delete styles.filter;
}
const duotoneDeclarations = getStylesDeclarations(duotoneStyles);
if (duotoneDeclarations.length) {
ruleset += `${duotoneSelector}{${duotoneDeclarations.join(';')};}`;
}
} // Process blockGap and layout styles.
if (!disableLayoutStyles && (_utils.ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport)) {
ruleset += getLayoutStyles({
tree,
style: styles,
selector,
hasBlockGapSupport,
hasFallbackGapSupport,
fallbackGapValue
});
} // Process the remaining block styles (they use either normal block class or __experimentalSelector).
const declarations = getStylesDeclarations(styles, selector, useRootPaddingAlign, tree);
if (declarations?.length) {
ruleset += `${selector}{${declarations.join(';')};}`;
} // Check for pseudo selector in `styles` and handle separately.
const pseudoSelectorStyles = Object.entries(styles).filter(([key]) => key.startsWith(':'));
if (pseudoSelectorStyles?.length) {
pseudoSelectorStyles.forEach(([pseudoKey, pseudoStyle]) => {
const pseudoDeclarations = getStylesDeclarations(pseudoStyle);
if (!pseudoDeclarations?.length) {
return;
} // `selector` maybe provided in a form
// where block level selectors have sub element
// selectors appended to them as a comma separated
// string.
// e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
// Split and append pseudo selector to create
// the proper rules to target the elements.
const _selector = selector.split(',').map(sel => sel + pseudoKey).join(',');
const pseudoRule = `${_selector}{${pseudoDeclarations.join(';')};}`;
ruleset += pseudoRule;
});
}
});
/* Add alignment / layout styles */
ruleset = ruleset + '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
ruleset = ruleset + '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
ruleset = ruleset + '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
if (hasBlockGapSupport) {
// Use fallback of `0.5em` just in case, however if there is blockGap support, there should nearly always be a real value.
const gapValue = (0, _gap.getGapCSSValue)(tree?.styles?.spacing?.blockGap) || '0.5em';
ruleset = ruleset + `:where(.wp-site-blocks) > * { margin-block-start: ${gapValue}; margin-block-end: 0; }`;
ruleset = ruleset + ':where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }';
ruleset = ruleset + ':where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }';
}
nodesWithSettings.forEach(({
selector,
presets
}) => {
if (_utils.ROOT_BLOCK_SELECTOR === selector) {
// Do not add extra specificity for top-level classes.
selector = '';
}
const classes = getPresetsClasses(selector, presets);
if (classes.length > 0) {
ruleset += classes;
}
});
return ruleset;
};
exports.toStyles = toStyles;
function toSvgFilters(tree, blockSelectors) {
const nodesWithSettings = getNodesWithSettings(tree, blockSelectors);
return nodesWithSettings.flatMap(({
presets
}) => {
return getPresetsSvgFilters(presets);
});
}
const getSelectorsConfig = (blockType, rootSelector) => {
if (blockType?.selectors && Object.keys(blockType.selectors).length > 0) {
return blockType.selectors;
}
const config = {
root: rootSelector
};
Object.entries(BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS).forEach(([featureKey, featureName]) => {
const featureSelector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType, featureKey);
if (featureSelector) {
config[featureName] = featureSelector;
}
});
return config;
};
const getBlockSelectors = (blockTypes, getBlockStyles) => {
const result = {};
blockTypes.forEach(blockType => {
const name = blockType.name;
const selector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType);
let duotoneSelector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType, 'filter.duotone'); // Keep backwards compatibility for support.color.__experimentalDuotone.
if (!duotoneSelector) {
const rootSelector = (0, _getBlockCssSelector.getBlockCSSSelector)(blockType);
const duotoneSupport = (0, _blocks.getBlockSupport)(blockType, 'color.__experimentalDuotone', false);
duotoneSelector = duotoneSupport && (0, _utils.scopeSelector)(rootSelector, duotoneSupport);
}
const hasLayoutSupport = !!blockType?.supports?.__experimentalLayout;
const fallbackGapValue = blockType?.supports?.spacing?.blockGap?.__experimentalDefault;
const blockStyleVariations = getBlockStyles(name);
const styleVariationSelectors = {};
if (blockStyleVariations?.length) {
blockStyleVariations.forEach(variation => {
const styleVariationSelector = `.is-style-${variation.name}${selector}`;
styleVariationSelectors[variation.name] = styleVariationSelector;
});
} // For each block support feature add any custom selectors.
const featureSelectors = getSelectorsConfig(blockType, selector);
result[name] = {
duotoneSelector,
fallbackGapValue,
featureSelectors: Object.keys(featureSelectors).length ? featureSelectors : undefined,
hasLayoutSupport,
name,
selector,
styleVariationSelectors: Object.keys(styleVariationSelectors).length ? styleVariationSelectors : undefined
};
});
return result;
};
/**
* If there is a separator block whose color is defined in theme.json via background,
* update the separator color to the same value by using border color.
*
* @param {Object} config Theme.json configuration file object.
* @return {Object} configTheme.json configuration file object updated.
*/
exports.getBlockSelectors = getBlockSelectors;
function updateConfigWithSeparator(config) {
const needsSeparatorStyleUpdate = config.styles?.blocks?.['core/separator'] && config.styles?.blocks?.['core/separator'].color?.background && !config.styles?.blocks?.['core/separator'].color?.text && !config.styles?.blocks?.['core/separator'].border?.color;
if (needsSeparatorStyleUpdate) {
return { ...config,
styles: { ...config.styles,
blocks: { ...config.styles.blocks,
'core/separator': { ...config.styles.blocks['core/separator'],
color: { ...config.styles.blocks['core/separator'].color,
text: config.styles?.blocks['core/separator'].color.background
}
}
}
}
};
}
return config;
}
const processCSSNesting = (css, blockSelector) => {
let processedCSS = ''; // Split CSS nested rules.
const parts = css.split('&');
parts.forEach(part => {
processedCSS += !part.includes('{') ? blockSelector + '{' + part + '}' // If the part doesn't contain braces, it applies to the root level.
: blockSelector + part; // Prepend the selector, which effectively replaces the "&" character.
});
return processedCSS;
};
/**
* Returns the global styles output using a global styles configuration.
* If wishing to generate global styles and settings based on the
* global styles config loaded in the editor context, use `useGlobalStylesOutput()`.
* The use case for a custom config is to generate bespoke styles
* and settings for previews, or other out-of-editor experiences.
*
* @param {Object} mergedConfig Global styles configuration.
* @return {Array} Array of stylesheets and settings.
*/
function useGlobalStylesOutputWithConfig(mergedConfig = {}) {
const [blockGap] = (0, _hooks.useGlobalSetting)('spacing.blockGap');
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 = (0, _data.useSelect)(select => {
const {
getSettings
} = select(_store.store);
return !!getSettings().disableLayoutStyles;
});
const getBlockStyles = (0, _data.useSelect)(select => {
return select(_blocks.store).getBlockStyles;
}, []);
return (0, _element.useMemo)(() => {
var _mergedConfig$styles$;
if (!mergedConfig?.styles || !mergedConfig?.settings) {
return [];
}
mergedConfig = updateConfigWithSeparator(mergedConfig);
const blockSelectors = getBlockSelectors((0, _blocks.getBlockTypes)(), getBlockStyles);
const customProperties = toCustomProperties(mergedConfig, blockSelectors);
const globalStyles = toStyles(mergedConfig, blockSelectors, hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles);
const svgs = toSvgFilters(mergedConfig, blockSelectors);
const styles = [{
css: customProperties,
isGlobalStyles: true
}, {
css: globalStyles,
isGlobalStyles: true
}, // Load custom CSS in own stylesheet so that any invalid CSS entered in the input won't break all the global styles in the editor.
{
css: (_mergedConfig$styles$ = mergedConfig.styles.css) !== null && _mergedConfig$styles$ !== void 0 ? _mergedConfig$styles$ : '',
isGlobalStyles: true
}, {
assets: svgs,
__unstableType: 'svg',
isGlobalStyles: true
}]; // Loop through the blocks to check if there are custom CSS values.
// If there are, get the block selector and push the selector together with
// the CSS value to the 'stylesheets' array.
(0, _blocks.getBlockTypes)().forEach(blockType => {
if (mergedConfig.styles.blocks[blockType.name]?.css) {
const selector = blockSelectors[blockType.name].selector;
styles.push({
css: processCSSNesting(mergedConfig.styles.blocks[blockType.name]?.css, selector),
isGlobalStyles: true
});
}
});
return [styles, mergedConfig.settings];
}, [hasBlockGapSupport, hasFallbackGapSupport, mergedConfig, disableLayoutStyles]);
}
/**
* Returns the global styles output based on the current state of global styles config loaded in the editor context.
*
* @return {Array} Array of stylesheets and settings.
*/
function useGlobalStylesOutput() {
const {
merged: mergedConfig
} = (0, _element.useContext)(_context.GlobalStylesContext);
return useGlobalStylesOutputWithConfig(mergedConfig);
}
//# sourceMappingURL=use-global-styles-output.js.map