@wordpress/block-editor
Version:
404 lines (389 loc) • 13.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.addSaveProps = addSaveProps;
exports.default = void 0;
exports.getInlineStyles = getInlineStyles;
exports.omitStyle = omitStyle;
var _element = require("@wordpress/element");
var _hooks = require("@wordpress/hooks");
var _blocks = require("@wordpress/blocks");
var _compose = require("@wordpress/compose");
var _styleEngine = require("@wordpress/style-engine");
var _background = require("./background");
var _border = require("./border");
var _color = require("./color");
var _typography = require("./typography");
var _dimensions = require("./dimensions");
var _utils = require("./utils");
var _utils2 = require("../components/global-styles/utils");
var _blockEditingMode = require("../components/block-editing-mode");
var _jsxRuntime = require("react/jsx-runtime");
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const styleSupportKeys = [..._typography.TYPOGRAPHY_SUPPORT_KEYS, _border.BORDER_SUPPORT_KEY, _color.COLOR_SUPPORT_KEY, _dimensions.DIMENSIONS_SUPPORT_KEY, _background.BACKGROUND_SUPPORT_KEY, _dimensions.SPACING_SUPPORT_KEY, _border.SHADOW_SUPPORT_KEY];
const hasStyleSupport = nameOrType => styleSupportKeys.some(key => (0, _blocks.hasBlockSupport)(nameOrType, key));
/**
* Returns the inline styles to add depending on the style object
*
* @param {Object} styles Styles configuration.
*
* @return {Object} Flattened CSS variables declaration.
*/
function getInlineStyles(styles = {}) {
const output = {};
// 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.
(0, _styleEngine.getCSSRules)(styles).forEach(rule => {
output[rule.key] = rule.value;
});
return output;
}
/**
* Filters registered block settings, extending attributes to include `style` attribute.
*
* @param {Object} settings Original block settings.
*
* @return {Object} Filtered block settings.
*/
function addAttribute(settings) {
if (!hasStyleSupport(settings)) {
return settings;
}
// Allow blocks to specify their own attribute definition with default values if needed.
if (!settings.attributes.style) {
Object.assign(settings.attributes, {
style: {
type: 'object'
}
});
}
return settings;
}
/**
* A dictionary of paths to flag skipping block support serialization as the key,
* with values providing the style paths to be omitted from serialization.
*
* @constant
* @type {Record<string, string[]>}
*/
const skipSerializationPathsEdit = {
[`${_border.BORDER_SUPPORT_KEY}.__experimentalSkipSerialization`]: ['border'],
[`${_color.COLOR_SUPPORT_KEY}.__experimentalSkipSerialization`]: [_color.COLOR_SUPPORT_KEY],
[`${_typography.TYPOGRAPHY_SUPPORT_KEY}.__experimentalSkipSerialization`]: [_typography.TYPOGRAPHY_SUPPORT_KEY],
[`${_dimensions.DIMENSIONS_SUPPORT_KEY}.__experimentalSkipSerialization`]: [_dimensions.DIMENSIONS_SUPPORT_KEY],
[`${_dimensions.SPACING_SUPPORT_KEY}.__experimentalSkipSerialization`]: [_dimensions.SPACING_SUPPORT_KEY],
[`${_border.SHADOW_SUPPORT_KEY}.__experimentalSkipSerialization`]: [_border.SHADOW_SUPPORT_KEY]
};
/**
* A dictionary of paths to flag skipping block support serialization as the key,
* with values providing the style paths to be omitted from serialization.
*
* Extends the Edit skip paths to enable skipping additional paths in just
* the Save component. This allows a block support to be serialized within the
* editor, while using an alternate approach, such as server-side rendering, when
* the support is saved.
*
* @constant
* @type {Record<string, string[]>}
*/
const skipSerializationPathsSave = {
...skipSerializationPathsEdit,
[`${_dimensions.DIMENSIONS_SUPPORT_KEY}.aspectRatio`]: [`${_dimensions.DIMENSIONS_SUPPORT_KEY}.aspectRatio`],
// Skip serialization of aspect ratio in save mode.
[`${_background.BACKGROUND_SUPPORT_KEY}`]: [_background.BACKGROUND_SUPPORT_KEY] // Skip serialization of background support in save mode.
};
const skipSerializationPathsSaveChecks = {
[`${_dimensions.DIMENSIONS_SUPPORT_KEY}.aspectRatio`]: true,
[`${_background.BACKGROUND_SUPPORT_KEY}`]: true
};
/**
* A dictionary used to normalize feature names between support flags, style
* object properties and __experimentSkipSerialization configuration arrays.
*
* This allows not having to provide a migration for a support flag and possible
* backwards compatibility bridges, while still achieving consistency between
* the support flag and the skip serialization array.
*
* @constant
* @type {Record<string, string>}
*/
const renamedFeatures = {
gradients: 'gradient'
};
/**
* A utility function used to remove one or more paths from a style object.
* Works in a way similar to Lodash's `omit()`. See unit tests and examples below.
*
* It supports a single string path:
*
* ```
* omitStyle( { color: 'red' }, 'color' ); // {}
* ```
*
* or an array of paths:
*
* ```
* omitStyle( { color: 'red', background: '#fff' }, [ 'color', 'background' ] ); // {}
* ```
*
* It also allows you to specify paths at multiple levels in a string.
*
* ```
* omitStyle( { typography: { textDecoration: 'underline' } }, 'typography.textDecoration' ); // {}
* ```
*
* You can remove multiple paths at the same time:
*
* ```
* omitStyle(
* {
* typography: {
* textDecoration: 'underline',
* textTransform: 'uppercase',
* }
* },
* [
* 'typography.textDecoration',
* 'typography.textTransform',
* ]
* );
* // {}
* ```
*
* You can also specify nested paths as arrays:
*
* ```
* omitStyle(
* {
* typography: {
* textDecoration: 'underline',
* textTransform: 'uppercase',
* }
* },
* [
* [ 'typography', 'textDecoration' ],
* [ 'typography', 'textTransform' ],
* ]
* );
* // {}
* ```
*
* With regards to nesting of styles, infinite depth is supported:
*
* ```
* omitStyle(
* {
* border: {
* radius: {
* topLeft: '10px',
* topRight: '0.5rem',
* }
* }
* },
* [
* [ 'border', 'radius', 'topRight' ],
* ]
* );
* // { border: { radius: { topLeft: '10px' } } }
* ```
*
* The third argument, `preserveReference`, defines how to treat the input style object.
* It is mostly necessary to properly handle mutation when recursively handling the style object.
* Defaulting to `false`, this will always create a new object, avoiding to mutate `style`.
* However, when recursing, we change that value to `true` in order to work with a single copy
* of the original style object.
*
* @see https://lodash.com/docs/4.17.15#omit
*
* @param {Object} style Styles object.
* @param {Array|string} paths Paths to remove.
* @param {boolean} preserveReference True to mutate the `style` object, false otherwise.
* @return {Object} Styles object with the specified paths removed.
*/
function omitStyle(style, paths, preserveReference = false) {
if (!style) {
return style;
}
let newStyle = style;
if (!preserveReference) {
newStyle = JSON.parse(JSON.stringify(style));
}
if (!Array.isArray(paths)) {
paths = [paths];
}
paths.forEach(path => {
if (!Array.isArray(path)) {
path = path.split('.');
}
if (path.length > 1) {
const [firstSubpath, ...restPath] = path;
omitStyle(newStyle[firstSubpath], [restPath], true);
} else if (path.length === 1) {
delete newStyle[path[0]];
}
});
return newStyle;
}
/**
* Override props assigned to save component to inject the CSS variables definition.
*
* @param {Object} props Additional props applied to save element.
* @param {Object|string} blockNameOrType Block type.
* @param {Object} attributes Block attributes.
* @param {?Record<string, string[]>} skipPaths An object of keys and paths to skip serialization.
*
* @return {Object} Filtered props applied to save element.
*/
function addSaveProps(props, blockNameOrType, attributes, skipPaths = skipSerializationPathsSave) {
if (!hasStyleSupport(blockNameOrType)) {
return props;
}
let {
style
} = attributes;
Object.entries(skipPaths).forEach(([indicator, path]) => {
const skipSerialization = skipSerializationPathsSaveChecks[indicator] || (0, _blocks.getBlockSupport)(blockNameOrType, indicator);
if (skipSerialization === true) {
style = omitStyle(style, path);
}
if (Array.isArray(skipSerialization)) {
skipSerialization.forEach(featureName => {
const feature = renamedFeatures[featureName] || featureName;
style = omitStyle(style, [[...path, feature]]);
});
}
});
props.style = {
...getInlineStyles(style),
...props.style
};
return props;
}
function BlockStyleControls({
clientId,
name,
setAttributes,
__unstableParentLayout
}) {
const settings = (0, _utils.useBlockSettings)(name, __unstableParentLayout);
const blockEditingMode = (0, _blockEditingMode.useBlockEditingMode)();
const passedProps = {
clientId,
name,
setAttributes,
settings: {
...settings,
typography: {
...settings.typography,
// The text alignment UI for individual blocks is rendered in
// the block toolbar, so disable it here.
textAlign: false
}
}
};
if (blockEditingMode !== 'default') {
return null;
}
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_color.ColorEdit, {
...passedProps
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_background.BackgroundImagePanel, {
...passedProps
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.TypographyPanel, {
...passedProps
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_border.BorderPanel, {
...passedProps
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_dimensions.DimensionsPanel, {
...passedProps
})]
});
}
var _default = exports.default = {
edit: BlockStyleControls,
hasSupport: hasStyleSupport,
addSaveProps,
attributeKeys: ['style'],
useBlockProps
}; // Defines which element types are supported, including their hover styles or
// any other elements that have been included under a single element type
// e.g. heading and h1-h6.
const elementTypes = [{
elementType: 'button'
}, {
elementType: 'link',
pseudo: [':hover']
}, {
elementType: 'heading',
elements: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
}];
// Used for generating the instance ID
const STYLE_BLOCK_PROPS_REFERENCE = {};
function useBlockProps({
name,
style
}) {
const blockElementsContainerIdentifier = (0, _compose.useInstanceId)(STYLE_BLOCK_PROPS_REFERENCE, 'wp-elements');
const baseElementSelector = `.${blockElementsContainerIdentifier}`;
const blockElementStyles = style?.elements;
const styles = (0, _element.useMemo)(() => {
if (!blockElementStyles) {
return;
}
const elementCSSRules = [];
elementTypes.forEach(({
elementType,
pseudo,
elements
}) => {
const skipSerialization = (0, _utils.shouldSkipSerialization)(name, _color.COLOR_SUPPORT_KEY, elementType);
if (skipSerialization) {
return;
}
const elementStyles = blockElementStyles?.[elementType];
// Process primary element type styles.
if (elementStyles) {
const selector = (0, _utils2.scopeSelector)(baseElementSelector, _blocks.__EXPERIMENTAL_ELEMENTS[elementType]);
elementCSSRules.push((0, _styleEngine.compileCSS)(elementStyles, {
selector
}));
// Process any interactive states for the element type.
if (pseudo) {
pseudo.forEach(pseudoSelector => {
if (elementStyles[pseudoSelector]) {
elementCSSRules.push((0, _styleEngine.compileCSS)(elementStyles[pseudoSelector], {
selector: (0, _utils2.scopeSelector)(baseElementSelector, `${_blocks.__EXPERIMENTAL_ELEMENTS[elementType]}${pseudoSelector}`)
}));
}
});
}
}
// Process related elements e.g. h1-h6 for headings
if (elements) {
elements.forEach(element => {
if (blockElementStyles[element]) {
elementCSSRules.push((0, _styleEngine.compileCSS)(blockElementStyles[element], {
selector: (0, _utils2.scopeSelector)(baseElementSelector, _blocks.__EXPERIMENTAL_ELEMENTS[element])
}));
}
});
}
});
return elementCSSRules.length > 0 ? elementCSSRules.join('') : undefined;
}, [baseElementSelector, blockElementStyles, name]);
(0, _utils.useStyleOverride)({
css: styles
});
return addSaveProps({
className: blockElementsContainerIdentifier
}, name, {
style
}, skipSerializationPathsEdit);
}
(0, _hooks.addFilter)('blocks.registerBlockType', 'core/style/addAttribute', addAttribute);
//# sourceMappingURL=style.js.map
;