/* wp:polyfill */
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _hooks = require("@wordpress/hooks");
var _compose = require("@wordpress/compose");
var _blockEditor = require("@wordpress/block-editor");
var _components = require("@wordpress/components");
var _i18n = require("@wordpress/i18n");
var _blocks = require("@wordpress/blocks");
var _element = require("@wordpress/element");
var _data = require("@wordpress/data");
var _notices = require("@wordpress/notices");
var _coreData = require("@wordpress/core-data");
var _hooks2 = require("../../components/global-styles/hooks");
var _lockUnlock = require("../../lock-unlock");
var _setNestedValue = _interopRequireDefault(require("../../utils/set-nested-value"));
var _jsxRuntime = require("react/jsx-runtime");
/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

const {
  cleanEmptyObject,
  GlobalStylesContext
} = (0, _lockUnlock.unlock)(_blockEditor.privateApis);

// Block Gap is a special case and isn't defined within the blocks
// style properties config. We'll add it here to allow it to be pushed
// to global styles as well.
const STYLE_PROPERTY = {
  ..._blocks.__EXPERIMENTAL_STYLE_PROPERTY,
  blockGap: {
    value: ['spacing', 'blockGap']
  }
};

// TODO: Temporary duplication of constant in @wordpress/block-editor. Can be
// removed by moving PushChangesToGlobalStylesControl to
// @wordpress/block-editor.
const 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'
};

// TODO: Temporary duplication of constant in @wordpress/block-editor. Can be
// removed by moving PushChangesToGlobalStylesControl to
// @wordpress/block-editor.
const STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE = {
  'border.color': 'borderColor',
  'color.background': 'backgroundColor',
  'color.text': 'textColor',
  'color.gradient': 'gradient',
  'typography.fontSize': 'fontSize',
  'typography.fontFamily': 'fontFamily'
};
const SUPPORTED_STYLES = ['border', 'color', 'spacing', 'typography'];
const getValueFromObjectPath = (object, path) => {
  let value = object;
  path.forEach(fieldName => {
    value = value?.[fieldName];
  });
  return value;
};
const flatBorderProperties = ['borderColor', 'borderWidth', 'borderStyle'];
const 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)];

  // Handle a flat border i.e. all sides the same, CSS shorthand.
  const {
    color: customColor,
    style,
    width
  } = border || {};
  const hasColorOrWidth = presetColor || customColor || width;
  if (hasColorOrWidth && !style) {
    // Global Styles need individual side configurations to overcome
    // theme.json configurations which are per side as well.
    sides.forEach(side => {
      // Only add fallback border-style if global styles don't already
      // have something set.
      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 = (0, _hooks2.useSupportedStyles)(name);
  const blockUserConfig = userConfig?.styles?.blocks?.[name];
  return (0, _element.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);

      // Links only have a single support entry but have two element
      // style properties, color and hover color. The following check
      // will add the hover color to the changes if required.
      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;
      }

      // The shorthand border styles can't be mapped directly as global
      // styles requires longhand config.
      if (flatBorderProperties.includes(key) && value) {
        // The shorthand config path is included to clear the block attribute.
        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
      }] : [];
    });

    // To ensure display of a visible border, global styles require a
    // default border style if a border color or width is present.
    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,
    setUserConfig
  } = (0, _element.useContext)(GlobalStylesContext);
  const changes = useChangesToPush(name, attributes, userConfig);
  const {
    __unstableMarkNextChangeAsNotPersistent
  } = (0, _data.useDispatch)(_blockEditor.store);
  const {
    createSuccessNotice
  } = (0, _data.useDispatch)(_notices.store);
  const pushChanges = (0, _element.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) {
        (0, _setNestedValue.default)(newBlockStyles, path, undefined);
        (0, _setNestedValue.default)(newUserConfig, ['styles', 'blocks', name, ...path], value);
      }
      const newBlockAttributes = {
        borderColor: undefined,
        backgroundColor: undefined,
        textColor: undefined,
        gradient: undefined,
        fontSize: undefined,
        fontFamily: undefined,
        style: cleanEmptyObject(newBlockStyles)
      };

      // @wordpress/core-data doesn't support editing multiple entity types in
      // a single undo level. So for now, we disable @wordpress/core-data undo
      // tracking and implement our own Undo button in the snackbar
      // notification.
      __unstableMarkNextChangeAsNotPersistent();
      setAttributes(newBlockAttributes);
      setUserConfig(newUserConfig, {
        undoIgnore: true
      });
      createSuccessNotice((0, _i18n.sprintf)(
      // translators: %s: Title of the block e.g. 'Heading'.
      (0, _i18n.__)('%s styles applied.'), (0, _blocks.getBlockType)(name).title), {
        type: 'snackbar',
        actions: [{
          label: (0, _i18n.__)('Undo'),
          onClick() {
            __unstableMarkNextChangeAsNotPersistent();
            setAttributes(attributes);
            setUserConfig(userConfig, {
              undoIgnore: true
            });
          }
        }]
      });
    }
  }, [__unstableMarkNextChangeAsNotPersistent, attributes, changes, createSuccessNotice, name, setAttributes, setUserConfig, userConfig]);
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.BaseControl, {
    __nextHasNoMarginBottom: true,
    className: "edit-site-push-changes-to-global-styles-control",
    help: (0, _i18n.sprintf)(
    // translators: %s: Title of the block e.g. 'Heading'.
    (0, _i18n.__)('Apply this block’s typography, spacing, dimensions, and color styles to all %s blocks.'), (0, _blocks.getBlockType)(name).title),
    children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.BaseControl.VisualLabel, {
      children: (0, _i18n.__)('Styles')
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
      __next40pxDefaultSize: true,
      variant: "secondary",
      accessibleWhenDisabled: true,
      disabled: changes.length === 0,
      onClick: pushChanges,
      children: (0, _i18n.__)('Apply globally')
    })]
  });
}
function PushChangesToGlobalStyles(props) {
  const blockEditingMode = (0, _blockEditor.useBlockEditingMode)();
  const isBlockBasedTheme = (0, _data.useSelect)(select => select(_coreData.store).getCurrentTheme()?.is_block_theme, []);
  const supportsStyles = SUPPORTED_STYLES.some(feature => (0, _blocks.hasBlockSupport)(props.name, feature));
  const isDisplayed = blockEditingMode === 'default' && supportsStyles && isBlockBasedTheme;
  if (!isDisplayed) {
    return null;
  }
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.InspectorAdvancedControls, {
    children: /*#__PURE__*/(0, _jsxRuntime.jsx)(PushChangesToGlobalStylesControl, {
      ...props
    })
  });
}
const withPushChangesToGlobalStyles = (0, _compose.createHigherOrderComponent)(BlockEdit => props => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(BlockEdit, {
    ...props
  }, "edit"), props.isSelected && /*#__PURE__*/(0, _jsxRuntime.jsx)(PushChangesToGlobalStyles, {
    ...props
  })]
}));
(0, _hooks.addFilter)('editor.BlockEdit', 'core/edit-site/push-changes-to-global-styles', withPushChangesToGlobalStyles);
//# sourceMappingURL=index.js.map