"use strict";

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}.skipSerialization`]: ['border'],
  [`${_color.COLOR_SUPPORT_KEY}.skipSerialization`]: [_color.COLOR_SUPPORT_KEY],
  [`${_typography.TYPOGRAPHY_SUPPORT_KEY}.skipSerialization`]: [_typography.TYPOGRAPHY_SUPPORT_KEY],
  [`${_dimensions.DIMENSIONS_SUPPORT_KEY}.skipSerialization`]: [_dimensions.DIMENSIONS_SUPPORT_KEY],
  [`${_dimensions.SPACING_SUPPORT_KEY}.skipSerialization`]: [_dimensions.SPACING_SUPPORT_KEY],
  [`${_border.SHADOW_SUPPORT_KEY}.skipSerialization`]: [_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