UNPKG

@wordpress/block-editor

Version:
457 lines (450 loc) 18.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.cleanEmptyObject = void 0; exports.createBlockEditFilter = createBlockEditFilter; exports.createBlockListBlockFilter = createBlockListBlockFilter; exports.createBlockSaveFilter = createBlockSaveFilter; exports.shouldSkipSerialization = shouldSkipSerialization; exports.transformStyles = transformStyles; exports.useBlockSettings = useBlockSettings; exports.usePrivateStyleOverride = usePrivateStyleOverride; exports.useStyleOverride = useStyleOverride; var _blocks = require("@wordpress/blocks"); var _element = require("@wordpress/element"); var _data = require("@wordpress/data"); var _compose = require("@wordpress/compose"); var _hooks = require("@wordpress/hooks"); var _context = require("../components/block-edit/context"); var _components = require("../components"); var _hooks2 = require("../components/global-styles/hooks"); var _object = require("../utils/object"); var _store = require("../store"); var _lockUnlock = require("../lock-unlock"); var _clsx = _interopRequireDefault(require("clsx")); var _jsxRuntime = require("react/jsx-runtime"); /** * WordPress dependencies */ /** * Internal dependencies */ /** * External dependencies */ /** * Removed falsy values from nested object. * * @param {*} object * @return {*} Object cleaned from falsy values */ const cleanEmptyObject = object => { if (object === null || typeof object !== 'object' || Array.isArray(object)) { return object; } const cleanedNestedObjects = Object.entries(object).map(([key, value]) => [key, cleanEmptyObject(value)]).filter(([, value]) => value !== undefined); return !cleanedNestedObjects.length ? undefined : Object.fromEntries(cleanedNestedObjects); }; exports.cleanEmptyObject = cleanEmptyObject; function transformStyles(activeSupports, migrationPaths, result, source, index, results) { // If there are no active supports return early. if (Object.values(activeSupports !== null && activeSupports !== void 0 ? activeSupports : {}).every(isActive => !isActive)) { return result; } // If the condition verifies we are probably in the presence of a wrapping transform // e.g: nesting paragraphs in a group or columns and in that case the styles should not be transformed. if (results.length === 1 && result.innerBlocks.length === source.length) { return result; } // For cases where we have a transform from one block to multiple blocks // or multiple blocks to one block we apply the styles of the first source block // to the result(s). let referenceBlockAttributes = source[0]?.attributes; // If we are in presence of transform between more than one block in the source // that has more than one block in the result // we apply the styles on source N to the result N, // if source N does not exists we do nothing. if (results.length > 1 && source.length > 1) { if (source[index]) { referenceBlockAttributes = source[index]?.attributes; } else { return result; } } let returnBlock = result; Object.entries(activeSupports).forEach(([support, isActive]) => { if (isActive) { migrationPaths[support].forEach(path => { const styleValue = (0, _object.getValueFromObjectPath)(referenceBlockAttributes, path); if (styleValue) { returnBlock = { ...returnBlock, attributes: (0, _object.setImmutably)(returnBlock.attributes, path, styleValue) }; } }); } }); return returnBlock; } /** * Check whether serialization of specific block support feature or set should * be skipped. * * @param {string|Object} blockNameOrType Block name or block type object. * @param {string} featureSet Name of block support feature set. * @param {string} feature Name of the individual feature to check. * * @return {boolean} Whether serialization should occur. */ function shouldSkipSerialization(blockNameOrType, featureSet, feature) { const support = (0, _blocks.getBlockSupport)(blockNameOrType, featureSet); const skipSerialization = support?.__experimentalSkipSerialization; if (Array.isArray(skipSerialization)) { return skipSerialization.includes(feature); } return skipSerialization; } const pendingStyleOverrides = new WeakMap(); /** * Override a block editor settings style. Leave the ID blank to create a new * style. * * @param {Object} override Override object. * @param {?string} override.id Id of the style override, leave blank to create * a new style. * @param {string} override.css CSS to apply. */ function useStyleOverride({ id, css }) { return usePrivateStyleOverride({ id, css }); } function usePrivateStyleOverride({ id, css, assets, __unstableType, variation, clientId } = {}) { const { setStyleOverride, deleteStyleOverride } = (0, _lockUnlock.unlock)((0, _data.useDispatch)(_store.store)); const registry = (0, _data.useRegistry)(); const fallbackId = (0, _element.useId)(); (0, _element.useEffect)(() => { // Unmount if there is CSS and assets are empty. if (!css && !assets) { return; } const _id = id || fallbackId; const override = { id, css, assets, __unstableType, variation, clientId }; // Batch updates to style overrides to avoid triggering cascading renders // for each style override block included in a tree and optimize initial render. if (!pendingStyleOverrides.get(registry)) { pendingStyleOverrides.set(registry, []); } pendingStyleOverrides.get(registry).push([_id, override]); window.queueMicrotask(() => { if (pendingStyleOverrides.get(registry)?.length) { registry.batch(() => { pendingStyleOverrides.get(registry).forEach(args => { setStyleOverride(...args); }); pendingStyleOverrides.set(registry, []); }); } }); return () => { const isPending = pendingStyleOverrides.get(registry)?.find(([currentId]) => currentId === _id); if (isPending) { pendingStyleOverrides.set(registry, pendingStyleOverrides.get(registry).filter(([currentId]) => currentId !== _id)); } else { deleteStyleOverride(_id); } }; }, [id, css, clientId, assets, __unstableType, fallbackId, setStyleOverride, deleteStyleOverride, registry]); } /** * Based on the block and its context, returns an object of all the block settings. * This object can be passed as a prop to all the Styles UI components * (TypographyPanel, DimensionsPanel...). * * @param {string} name Block name. * @param {*} parentLayout Parent layout. * * @return {Object} Settings object. */ function useBlockSettings(name, parentLayout) { const [backgroundImage, backgroundSize, customFontFamilies, defaultFontFamilies, themeFontFamilies, defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, customFontSize, fontStyle, fontWeight, lineHeight, textAlign, textColumns, textDecoration, writingMode, textTransform, letterSpacing, padding, margin, blockGap, defaultSpacingSizesEnabled, customSpacingSize, userSpacingSizes, defaultSpacingSizes, themeSpacingSizes, units, aspectRatio, minHeight, layout, borderColor, borderRadius, borderStyle, borderWidth, customColorsEnabled, customColors, customDuotone, themeColors, defaultColors, defaultPalette, defaultDuotone, userDuotonePalette, themeDuotonePalette, defaultDuotonePalette, userGradientPalette, themeGradientPalette, defaultGradientPalette, defaultGradients, areCustomGradientsEnabled, isBackgroundEnabled, isLinkEnabled, isTextEnabled, isHeadingEnabled, isButtonEnabled, shadow] = (0, _components.useSettings)('background.backgroundImage', 'background.backgroundSize', 'typography.fontFamilies.custom', 'typography.fontFamilies.default', 'typography.fontFamilies.theme', 'typography.defaultFontSizes', 'typography.fontSizes.custom', 'typography.fontSizes.default', 'typography.fontSizes.theme', 'typography.customFontSize', 'typography.fontStyle', 'typography.fontWeight', 'typography.lineHeight', 'typography.textAlign', 'typography.textColumns', 'typography.textDecoration', 'typography.writingMode', 'typography.textTransform', 'typography.letterSpacing', 'spacing.padding', 'spacing.margin', 'spacing.blockGap', 'spacing.defaultSpacingSizes', 'spacing.customSpacingSize', 'spacing.spacingSizes.custom', 'spacing.spacingSizes.default', 'spacing.spacingSizes.theme', 'spacing.units', 'dimensions.aspectRatio', 'dimensions.minHeight', 'layout', 'border.color', 'border.radius', 'border.style', 'border.width', 'color.custom', 'color.palette.custom', 'color.customDuotone', 'color.palette.theme', 'color.palette.default', 'color.defaultPalette', 'color.defaultDuotone', 'color.duotone.custom', 'color.duotone.theme', 'color.duotone.default', 'color.gradients.custom', 'color.gradients.theme', 'color.gradients.default', 'color.defaultGradients', 'color.customGradient', 'color.background', 'color.link', 'color.text', 'color.heading', 'color.button', 'shadow'); const rawSettings = (0, _element.useMemo)(() => { return { background: { backgroundImage, backgroundSize }, color: { palette: { custom: customColors, theme: themeColors, default: defaultColors }, gradients: { custom: userGradientPalette, theme: themeGradientPalette, default: defaultGradientPalette }, duotone: { custom: userDuotonePalette, theme: themeDuotonePalette, default: defaultDuotonePalette }, defaultGradients, defaultPalette, defaultDuotone, custom: customColorsEnabled, customGradient: areCustomGradientsEnabled, customDuotone, background: isBackgroundEnabled, link: isLinkEnabled, heading: isHeadingEnabled, button: isButtonEnabled, text: isTextEnabled }, typography: { fontFamilies: { custom: customFontFamilies, default: defaultFontFamilies, theme: themeFontFamilies }, fontSizes: { custom: customFontSizes, default: defaultFontSizes, theme: themeFontSizes }, customFontSize, defaultFontSizes: defaultFontSizesEnabled, fontStyle, fontWeight, lineHeight, textAlign, textColumns, textDecoration, textTransform, letterSpacing, writingMode }, spacing: { spacingSizes: { custom: userSpacingSizes, default: defaultSpacingSizes, theme: themeSpacingSizes }, customSpacingSize, defaultSpacingSizes: defaultSpacingSizesEnabled, padding, margin, blockGap, units }, border: { color: borderColor, radius: borderRadius, style: borderStyle, width: borderWidth }, dimensions: { aspectRatio, minHeight }, layout, parentLayout, shadow }; }, [backgroundImage, backgroundSize, customFontFamilies, defaultFontFamilies, themeFontFamilies, defaultFontSizesEnabled, customFontSizes, defaultFontSizes, themeFontSizes, customFontSize, fontStyle, fontWeight, lineHeight, textAlign, textColumns, textDecoration, textTransform, letterSpacing, writingMode, padding, margin, blockGap, defaultSpacingSizesEnabled, customSpacingSize, userSpacingSizes, defaultSpacingSizes, themeSpacingSizes, units, aspectRatio, minHeight, layout, parentLayout, borderColor, borderRadius, borderStyle, borderWidth, customColorsEnabled, customColors, customDuotone, themeColors, defaultColors, defaultPalette, defaultDuotone, userDuotonePalette, themeDuotonePalette, defaultDuotonePalette, userGradientPalette, themeGradientPalette, defaultGradientPalette, defaultGradients, areCustomGradientsEnabled, isBackgroundEnabled, isLinkEnabled, isTextEnabled, isHeadingEnabled, isButtonEnabled, shadow]); return (0, _hooks2.useSettingsForBlockElement)(rawSettings, name); } function createBlockEditFilter(features) { // We don't want block controls to re-render when typing inside a block. // `memo` will prevent re-renders unless props change, so only pass the // needed props and not the whole attributes object. features = features.map(settings => { return { ...settings, Edit: (0, _element.memo)(settings.edit) }; }); const withBlockEditHooks = (0, _compose.createHigherOrderComponent)(OriginalBlockEdit => props => { const context = (0, _context.useBlockEditContext)(); // CAUTION: code added before this line will be executed for all // blocks, not just those that support the feature! Code added // above this line should be carefully evaluated for its impact on // performance. return [...features.map((feature, i) => { const { Edit, hasSupport, attributeKeys = [], shareWithChildBlocks } = feature; const shouldDisplayControls = context[_context.mayDisplayControlsKey] || context[_context.mayDisplayParentControlsKey] && shareWithChildBlocks; if (!shouldDisplayControls || !hasSupport(props.name)) { return null; } const neededProps = {}; for (const key of attributeKeys) { if (props.attributes[key]) { neededProps[key] = props.attributes[key]; } } return /*#__PURE__*/(0, _jsxRuntime.jsx)(Edit // We can use the index because the array length // is fixed per page load right now. , { name: props.name, isSelected: props.isSelected, clientId: props.clientId, setAttributes: props.setAttributes, __unstableParentLayout: props.__unstableParentLayout // This component is pure, so only pass needed // props!!! , ...neededProps }, i); }), /*#__PURE__*/(0, _jsxRuntime.jsx)(OriginalBlockEdit, { ...props }, "edit")]; }, 'withBlockEditHooks'); (0, _hooks.addFilter)('editor.BlockEdit', 'core/editor/hooks', withBlockEditHooks); } function BlockProps({ index, useBlockProps: hook, setAllWrapperProps, ...props }) { const wrapperProps = hook(props); const setWrapperProps = next => setAllWrapperProps(prev => { const nextAll = [...prev]; nextAll[index] = next; return nextAll; }); // Setting state after every render is fine because this component is // pure and will only re-render when needed props change. (0, _element.useEffect)(() => { // We could shallow compare the props, but since this component only // changes when needed attributes change, the benefit is probably small. setWrapperProps(wrapperProps); return () => { setWrapperProps(undefined); }; }); return null; } const BlockPropsPure = (0, _element.memo)(BlockProps); function createBlockListBlockFilter(features) { const withBlockListBlockHooks = (0, _compose.createHigherOrderComponent)(BlockListBlock => props => { const [allWrapperProps, setAllWrapperProps] = (0, _element.useState)(Array(features.length).fill(undefined)); return [...features.map((feature, i) => { const { hasSupport, attributeKeys = [], useBlockProps, isMatch } = feature; const neededProps = {}; for (const key of attributeKeys) { if (props.attributes[key]) { neededProps[key] = props.attributes[key]; } } if ( // Skip rendering if none of the needed attributes are // set. !Object.keys(neededProps).length || !hasSupport(props.name) || isMatch && !isMatch(neededProps)) { return null; } return /*#__PURE__*/(0, _jsxRuntime.jsx)(BlockPropsPure // We can use the index because the array length // is fixed per page load right now. , { index: i, useBlockProps: useBlockProps // This component is pure, so we must pass a stable // function reference. , setAllWrapperProps: setAllWrapperProps, name: props.name, clientId: props.clientId // This component is pure, so only pass needed // props!!! , ...neededProps }, i); }), /*#__PURE__*/(0, _jsxRuntime.jsx)(BlockListBlock, { ...props, wrapperProps: allWrapperProps.filter(Boolean).reduce((acc, wrapperProps) => { return { ...acc, ...wrapperProps, className: (0, _clsx.default)(acc.className, wrapperProps.className), style: { ...acc.style, ...wrapperProps.style } }; }, props.wrapperProps || {}) }, "edit")]; }, 'withBlockListBlockHooks'); (0, _hooks.addFilter)('editor.BlockListBlock', 'core/editor/hooks', withBlockListBlockHooks); } function createBlockSaveFilter(features) { function extraPropsFromHooks(props, name, attributes) { return features.reduce((accu, feature) => { const { hasSupport, attributeKeys = [], addSaveProps } = feature; const neededAttributes = {}; for (const key of attributeKeys) { if (attributes[key]) { neededAttributes[key] = attributes[key]; } } if ( // Skip rendering if none of the needed attributes are // set. !Object.keys(neededAttributes).length || !hasSupport(name)) { return accu; } return addSaveProps(accu, name, neededAttributes); }, props); } (0, _hooks.addFilter)('blocks.getSaveContent.extraProps', 'core/editor/hooks', extraPropsFromHooks, 0); (0, _hooks.addFilter)('blocks.getSaveContent.extraProps', 'core/editor/hooks', props => { // Previously we had a filter deleting the className if it was an empty // string. That filter is no longer running, so now we need to delete it // here. if (props.hasOwnProperty('className') && !props.className) { delete props.className; } return props; }); } //# sourceMappingURL=utils.js.map