UNPKG

@wordpress/editor

Version:
435 lines (434 loc) 13.8 kB
// 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, useState } 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 ApplyGloballyModal from "./apply-globally-modal.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 sides = ["top", "right", "bottom", "left"]; function getBorderRows(supports, attributes, blockUserConfig) { const rows = []; const border = attributes.style?.border; const userBorder = blockUserConfig?.border; const colorSupported = supports.includes("borderColor"); const widthSupported = supports.includes("borderWidth"); const styleSupported = supports.includes("borderStyle"); const presetBorderColor = attributes.borderColor; const flatColor = presetBorderColor ? `var:preset|color|${presetBorderColor}` : border?.color; const flatRow = buildBorderScopeRow({ id: "border", primaryPath: ["border"], side: null, color: colorSupported ? flatColor : void 0, width: widthSupported ? border?.width : void 0, style: styleSupported ? border?.style : void 0, userBorder, presetAttributes: presetBorderColor ? ["borderColor"] : [] }); if (flatRow) { rows.push(flatRow); } sides.forEach((side) => { const sideBorder = border?.[side]; const sideRow = buildBorderScopeRow({ id: `border.${side}`, primaryPath: ["border", side], side, color: colorSupported ? sideBorder?.color : void 0, width: widthSupported ? sideBorder?.width : void 0, style: styleSupported ? sideBorder?.style : void 0, userBorder, presetAttributes: [] }); if (sideRow) { rows.push(sideRow); } }); if (supports.includes("borderRadius") && border?.radius !== void 0 && border?.radius !== "") { rows.push({ id: "border.radius", primaryPath: ["border", "radius"], paths: [{ path: ["border", "radius"], value: border.radius }], presetAttributes: [], newValue: border.radius, format: "borderRadius" }); } return rows; } function buildBorderScopeRow({ id, primaryPath, side, color, width, style, userBorder, presetAttributes }) { if (!color && !width && !style) { return null; } const paths = []; const targetSides = side ? [side] : sides; const addChange = (property, value) => { if (value === void 0) { return; } if (!side) { paths.push({ path: ["border", property], value }); } targetSides.forEach((targetSide) => { paths.push({ path: ["border", targetSide, property], value }); }); }; addChange("color", color); addChange("width", width); addChange("style", style); let effectiveStyle = style; if (!style && (color || width)) { const sideStyles = targetSides.map( (targetSide) => userBorder?.[targetSide]?.style ); targetSides.forEach((targetSide, index) => { if (!sideStyles[index]) { paths.push({ path: ["border", targetSide, "style"], value: "solid" }); } }); const sharedStyle = sideStyles.every( (sideStyle) => sideStyle === sideStyles[0] ) ? sideStyles[0] : void 0; effectiveStyle = sharedStyle || "solid"; } return { id, primaryPath, paths, presetAttributes, newValue: { color, width, style: effectiveStyle }, format: "border" }; } function getChangesToPush(supports, attributes, blockUserConfig) { const rows = []; supports.forEach((key) => { if (!STYLE_PROPERTY[key]) { return; } if (key.startsWith("border")) { return; } if (STYLE_PROPERTY[key].rootOnly) { return; } const { value: path } = STYLE_PROPERTY[key]; const presetAttributeKey = path.join("."); const presetAttributeName = STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE[presetAttributeKey]; const presetAttributeValue = presetAttributeName ? attributes[presetAttributeName] : void 0; const value = presetAttributeValue ? `var:preset|${STYLE_PATH_TO_CSS_VAR_INFIX[presetAttributeKey]}|${presetAttributeValue}` : getValueFromObjectPath(attributes.style, path); const presetAttributes = presetAttributeValue ? [presetAttributeName] : []; if (key === "linkColor") { const paths = value ? [{ path, value }] : []; const hoverPath = ["elements", "link", ":hover", "color", "text"]; const hoverValue = getValueFromObjectPath( attributes.style, hoverPath ); if (hoverValue) { paths.push({ path: hoverPath, value: hoverValue }); } if (paths.length === 0) { return; } rows.push({ id: presetAttributeKey, primaryPath: path, paths, presetAttributes, newValue: value ?? hoverValue }); return; } if (value) { let format; if (key === "padding" || key === "margin") { format = "spacing"; } else if (key === "blockGap") { format = "blockGap"; } rows.push({ id: presetAttributeKey, primaryPath: path, paths: [{ path, value }], presetAttributes, newValue: value, format }); } }); rows.push(...getBorderRows(supports, attributes, blockUserConfig)); return rows; } function useChangesToPush(name, attributes, userConfig) { const supports = useSelect( (select) => { return unlock(select(blocksStore)).getSupportedStyles(name); }, [name] ); const blockUserConfig = userConfig?.styles?.blocks?.[name]; return useMemo( () => getChangesToPush(supports, attributes, blockUserConfig), [supports, attributes, blockUserConfig] ); } function getStylesUpdate({ rowsToPush, attributes, userConfig, name }) { if (!rowsToPush || rowsToPush.length === 0) { return null; } const selectedChanges = rowsToPush.flatMap((row) => row.paths); if (selectedChanges.length === 0) { return null; } const { style: blockStyles } = attributes; const newBlockStyles = structuredClone(blockStyles); const newUserConfig = structuredClone(userConfig); for (const { path, value } of selectedChanges) { setNestedValue(newBlockStyles, path, void 0); setNestedValue( newUserConfig, ["styles", "blocks", name, ...path], value ); } const newBlockAttributes = { style: cleanEmptyObject(newBlockStyles) }; for (const presetAttribute of rowsToPush.flatMap( (row) => row.presetAttributes )) { newBlockAttributes[presetAttribute] = void 0; } return { newBlockAttributes, newUserConfig }; } function PushChangesToGlobalStylesControl({ name, attributes, setAttributes }) { const { user: userConfig, setUser: setUserConfig } = useGlobalStyles(); const rows = useChangesToPush(name, attributes, userConfig); const [isModalOpen, setIsModalOpen] = useState(false); const { __unstableMarkNextChangeAsNotPersistent } = useDispatch(blockEditorStore); const { createSuccessNotice } = useDispatch(noticesStore); const pushChanges = useCallback( (rowsToPush) => { const update = getStylesUpdate({ rowsToPush, attributes, userConfig, name }); if (!update) { return; } const { newBlockAttributes, newUserConfig } = update; __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, 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'. __( "Review and apply this block’s 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: rows.length === 0, onClick: () => setIsModalOpen(true), children: __("Apply globally") } ), isModalOpen && /* @__PURE__ */ jsx( ApplyGloballyModal, { name, rows, onApply: pushChanges, onRequestClose: () => setIsModalOpen(false) } ) ] } ); } 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 ); export { getChangesToPush, getStylesUpdate }; //# sourceMappingURL=index.mjs.map