@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
310 lines (309 loc) • 10.7 kB
JavaScript
// packages/editor/src/hooks/push-changes-to-global-styles/index.js
import { addFilter } from "@wordpress/hooks";
import { createHigherOrderComponent } from "@wordpress/compose";
import {
InspectorAdvancedControls,
store as blockEditorStore,
privateApis as blockEditorPrivateApis,
useBlockEditingMode
} from "@wordpress/block-editor";
import { BaseControl, Button } from "@wordpress/components";
import { __, sprintf } from "@wordpress/i18n";
import {
__EXPERIMENTAL_STYLE_PROPERTY,
getBlockType,
hasBlockSupport,
store as blocksStore
} from "@wordpress/blocks";
import { useMemo, useCallback } from "@wordpress/element";
import { useDispatch, useSelect } from "@wordpress/data";
import { store as noticesStore } from "@wordpress/notices";
import { store as coreStore } from "@wordpress/core-data";
import { unlock } from "../../lock-unlock.mjs";
import setNestedValue from "../../utils/set-nested-value.mjs";
import { useGlobalStyles } from "../../components/global-styles/hooks.mjs";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var { cleanEmptyObject } = unlock(blockEditorPrivateApis);
var STYLE_PROPERTY = {
...__EXPERIMENTAL_STYLE_PROPERTY,
blockGap: { value: ["spacing", "blockGap"] }
};
var STYLE_PATH_TO_CSS_VAR_INFIX = {
"border.color": "color",
"color.background": "color",
"color.text": "color",
"elements.link.color.text": "color",
"elements.link.:hover.color.text": "color",
"elements.link.typography.fontFamily": "font-family",
"elements.link.typography.fontSize": "font-size",
"elements.button.color.text": "color",
"elements.button.color.background": "color",
"elements.button.typography.fontFamily": "font-family",
"elements.button.typography.fontSize": "font-size",
"elements.caption.color.text": "color",
"elements.heading.color": "color",
"elements.heading.color.background": "color",
"elements.heading.typography.fontFamily": "font-family",
"elements.heading.gradient": "gradient",
"elements.heading.color.gradient": "gradient",
"elements.h1.color": "color",
"elements.h1.color.background": "color",
"elements.h1.typography.fontFamily": "font-family",
"elements.h1.color.gradient": "gradient",
"elements.h2.color": "color",
"elements.h2.color.background": "color",
"elements.h2.typography.fontFamily": "font-family",
"elements.h2.color.gradient": "gradient",
"elements.h3.color": "color",
"elements.h3.color.background": "color",
"elements.h3.typography.fontFamily": "font-family",
"elements.h3.color.gradient": "gradient",
"elements.h4.color": "color",
"elements.h4.color.background": "color",
"elements.h4.typography.fontFamily": "font-family",
"elements.h4.color.gradient": "gradient",
"elements.h5.color": "color",
"elements.h5.color.background": "color",
"elements.h5.typography.fontFamily": "font-family",
"elements.h5.color.gradient": "gradient",
"elements.h6.color": "color",
"elements.h6.color.background": "color",
"elements.h6.typography.fontFamily": "font-family",
"elements.h6.color.gradient": "gradient",
"color.gradient": "gradient",
blockGap: "spacing",
"typography.fontSize": "font-size",
"typography.fontFamily": "font-family"
};
var STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE = {
"border.color": "borderColor",
"color.background": "backgroundColor",
"color.text": "textColor",
"color.gradient": "gradient",
"typography.fontSize": "fontSize",
"typography.fontFamily": "fontFamily"
};
var SUPPORTED_STYLES = ["border", "color", "spacing", "typography"];
var getValueFromObjectPath = (object, path) => {
let value = object;
path.forEach((fieldName) => {
value = value?.[fieldName];
});
return value;
};
var flatBorderProperties = ["borderColor", "borderWidth", "borderStyle"];
var sides = ["top", "right", "bottom", "left"];
function getBorderStyleChanges(border, presetColor, userStyle) {
if (!border && !presetColor) {
return [];
}
const changes = [
...getFallbackBorderStyleChange("top", border, userStyle),
...getFallbackBorderStyleChange("right", border, userStyle),
...getFallbackBorderStyleChange("bottom", border, userStyle),
...getFallbackBorderStyleChange("left", border, userStyle)
];
const { color: customColor, style, width } = border || {};
const hasColorOrWidth = presetColor || customColor || width;
if (hasColorOrWidth && !style) {
sides.forEach((side) => {
if (!userStyle?.[side]?.style) {
changes.push({
path: ["border", side, "style"],
value: "solid"
});
}
});
}
return changes;
}
function getFallbackBorderStyleChange(side, border, globalBorderStyle) {
if (!border?.[side] || globalBorderStyle?.[side]?.style) {
return [];
}
const { color, style, width } = border[side];
const hasColorOrWidth = color || width;
if (!hasColorOrWidth || style) {
return [];
}
return [{ path: ["border", side, "style"], value: "solid" }];
}
function useChangesToPush(name, attributes, userConfig) {
const supports = useSelect(
(select) => {
return unlock(select(blocksStore)).getSupportedStyles(name);
},
[name]
);
const blockUserConfig = userConfig?.styles?.blocks?.[name];
return useMemo(() => {
const changes = supports.flatMap((key) => {
if (!STYLE_PROPERTY[key]) {
return [];
}
const { value: path } = STYLE_PROPERTY[key];
const presetAttributeKey = path.join(".");
const presetAttributeValue = attributes[STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE[presetAttributeKey]];
const value = presetAttributeValue ? `var:preset|${STYLE_PATH_TO_CSS_VAR_INFIX[presetAttributeKey]}|${presetAttributeValue}` : getValueFromObjectPath(attributes.style, path);
if (key === "linkColor") {
const linkChanges = value ? [{ path, value }] : [];
const hoverPath = [
"elements",
"link",
":hover",
"color",
"text"
];
const hoverValue = getValueFromObjectPath(
attributes.style,
hoverPath
);
if (hoverValue) {
linkChanges.push({ path: hoverPath, value: hoverValue });
}
return linkChanges;
}
if (flatBorderProperties.includes(key) && value) {
const borderChanges = [{ path, value }];
sides.forEach((side) => {
const currentPath = [...path];
currentPath.splice(-1, 0, side);
borderChanges.push({ path: currentPath, value });
});
return borderChanges;
}
return value ? [{ path, value }] : [];
});
getBorderStyleChanges(
attributes.style?.border,
attributes.borderColor,
blockUserConfig?.border
).forEach((change) => changes.push(change));
return changes;
}, [supports, attributes, blockUserConfig]);
}
function PushChangesToGlobalStylesControl({
name,
attributes,
setAttributes
}) {
const { user: userConfig, setUser: setUserConfig } = useGlobalStyles();
const changes = useChangesToPush(name, attributes, userConfig);
const { __unstableMarkNextChangeAsNotPersistent } = useDispatch(blockEditorStore);
const { createSuccessNotice } = useDispatch(noticesStore);
const pushChanges = useCallback(() => {
if (changes.length === 0) {
return;
}
if (changes.length > 0) {
const { style: blockStyles } = attributes;
const newBlockStyles = structuredClone(blockStyles);
const newUserConfig = structuredClone(userConfig);
for (const { path, value } of changes) {
setNestedValue(newBlockStyles, path, void 0);
setNestedValue(
newUserConfig,
["styles", "blocks", name, ...path],
value
);
}
const newBlockAttributes = {
borderColor: void 0,
backgroundColor: void 0,
textColor: void 0,
gradient: void 0,
fontSize: void 0,
fontFamily: void 0,
style: cleanEmptyObject(newBlockStyles)
};
__unstableMarkNextChangeAsNotPersistent();
setAttributes(newBlockAttributes);
setUserConfig(newUserConfig, { undoIgnore: true });
createSuccessNotice(
sprintf(
// translators: %s: Title of the block e.g. 'Heading'.
__("%s styles applied."),
getBlockType(name).title
),
{
type: "snackbar",
actions: [
{
label: __("Undo"),
onClick() {
__unstableMarkNextChangeAsNotPersistent();
setAttributes(attributes);
setUserConfig(userConfig, {
undoIgnore: true
});
}
}
]
}
);
}
}, [
__unstableMarkNextChangeAsNotPersistent,
attributes,
changes,
createSuccessNotice,
name,
setAttributes,
setUserConfig,
userConfig
]);
return /* @__PURE__ */ jsxs(
BaseControl,
{
className: "editor-push-changes-to-global-styles-control",
help: sprintf(
// translators: %s: Title of the block e.g. 'Heading'.
__(
"Apply this block\u2019s typography, spacing, dimensions, and color styles to all %s blocks."
),
getBlockType(name).title
),
children: [
/* @__PURE__ */ jsx(BaseControl.VisualLabel, { children: __("Styles") }),
/* @__PURE__ */ jsx(
Button,
{
__next40pxDefaultSize: true,
variant: "secondary",
accessibleWhenDisabled: true,
disabled: changes.length === 0,
onClick: pushChanges,
children: __("Apply globally")
}
)
]
}
);
}
function PushChangesToGlobalStyles(props) {
const blockEditingMode = useBlockEditingMode();
const isBlockBasedTheme = useSelect(
(select) => select(coreStore).getCurrentTheme()?.is_block_theme,
[]
);
const supportsStyles = SUPPORTED_STYLES.some(
(feature) => hasBlockSupport(props.name, feature)
);
const isDisplayed = blockEditingMode === "default" && supportsStyles && isBlockBasedTheme;
if (!isDisplayed) {
return null;
}
return /* @__PURE__ */ jsx(InspectorAdvancedControls, { children: /* @__PURE__ */ jsx(PushChangesToGlobalStylesControl, { ...props }) });
}
var withPushChangesToGlobalStyles = createHigherOrderComponent(
(BlockEdit) => (props) => /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(BlockEdit, { ...props }, "edit"),
props.isSelected && /* @__PURE__ */ jsx(PushChangesToGlobalStyles, { ...props })
] })
);
addFilter(
"editor.BlockEdit",
"core/editor/push-changes-to-global-styles",
withPushChangesToGlobalStyles
);
//# sourceMappingURL=index.mjs.map