UNPKG

@wordpress/block-editor

Version:
325 lines (288 loc) 11.9 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { createElement, Fragment } from "@wordpress/element"; /** * External dependencies */ import classnames from 'classnames'; import { extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; /** * WordPress dependencies */ import { getBlockSupport, getBlockType, hasBlockSupport } from '@wordpress/blocks'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { useMemo, useContext, createPortal } from '@wordpress/element'; /** * Internal dependencies */ import { BlockControls, InspectorControls, __experimentalDuotoneControl as DuotoneControl, useSetting } from '../components'; import BlockList from '../components/block-list'; import { __unstableDuotoneFilter as DuotoneFilter, __unstableDuotoneStylesheet as DuotoneStylesheet, __unstableDuotoneUnsetStylesheet as DuotoneUnsetStylesheet } from '../components/duotone'; import { getBlockCSSSelector } from '../components/global-styles/get-block-css-selector'; import { scopeSelector } from '../components/global-styles/utils'; import { useBlockSettings } from './utils'; import { default as StylesFiltersPanel } from '../components/global-styles/filters-panel'; import { useBlockEditingMode } from '../components/block-editing-mode'; const EMPTY_ARRAY = []; extend([namesPlugin]); /** * SVG and stylesheet needed for rendering the duotone filter. * * @param {Object} props Duotone props. * @param {string} props.selector Selector to apply the filter to. * @param {string} props.id Unique id for this duotone filter. * @param {string[]|"unset"} props.colors Array of RGB color strings ordered from dark to light. * * @return {WPElement} Duotone element. */ function InlineDuotone({ selector, id, colors }) { if (colors === 'unset') { return createElement(DuotoneUnsetStylesheet, { selector: selector }); } return createElement(Fragment, null, createElement(DuotoneFilter, { id: id, colors: colors }), createElement(DuotoneStylesheet, { id: id, selector: selector })); } function useMultiOriginPresets({ presetSetting, defaultSetting }) { const disableDefault = !useSetting(defaultSetting); const userPresets = useSetting(`${presetSetting}.custom`) || EMPTY_ARRAY; const themePresets = useSetting(`${presetSetting}.theme`) || EMPTY_ARRAY; const defaultPresets = useSetting(`${presetSetting}.default`) || EMPTY_ARRAY; return useMemo(() => [...userPresets, ...themePresets, ...(disableDefault ? EMPTY_ARRAY : defaultPresets)], [disableDefault, userPresets, themePresets, defaultPresets]); } export function getColorsFromDuotonePreset(duotone, duotonePalette) { if (!duotone) { return; } const preset = duotonePalette?.find(({ slug }) => { return duotone === `var:preset|duotone|${slug}`; }); return preset ? preset.colors : undefined; } export function getDuotonePresetFromColors(colors, duotonePalette) { if (!colors || !Array.isArray(colors)) { return; } const preset = duotonePalette?.find(duotonePreset => { return duotonePreset?.colors?.every((val, index) => val === colors[index]); }); return preset ? `var:preset|duotone|${preset.slug}` : undefined; } function DuotonePanel({ attributes, setAttributes, name }) { const style = attributes?.style; const duotoneStyle = style?.color?.duotone; const settings = useBlockSettings(name); const duotonePalette = useMultiOriginPresets({ presetSetting: 'color.duotone', defaultSetting: 'color.defaultDuotone' }); const colorPalette = useMultiOriginPresets({ presetSetting: 'color.palette', defaultSetting: 'color.defaultPalette' }); const disableCustomColors = !useSetting('color.custom'); const disableCustomDuotone = !useSetting('color.customDuotone') || colorPalette?.length === 0 && disableCustomColors; if (duotonePalette?.length === 0 && disableCustomDuotone) { return null; } const duotonePresetOrColors = !Array.isArray(duotoneStyle) ? getColorsFromDuotonePreset(duotoneStyle, duotonePalette) : duotoneStyle; return createElement(Fragment, null, createElement(InspectorControls, { group: "filter" }, createElement(StylesFiltersPanel, { value: { filter: { duotone: duotonePresetOrColors } }, onChange: newDuotone => { const newStyle = { ...style, color: { ...newDuotone?.filter } }; setAttributes({ style: newStyle }); }, settings: settings })), createElement(BlockControls, { group: "block", __experimentalShareWithChildBlocks: true }, createElement(DuotoneControl, { duotonePalette: duotonePalette, colorPalette: colorPalette, disableCustomDuotone: disableCustomDuotone, disableCustomColors: disableCustomColors, value: duotonePresetOrColors, onChange: newDuotone => { const maybePreset = getDuotonePresetFromColors(newDuotone, duotonePalette); const newStyle = { ...style, color: { ...style?.color, duotone: maybePreset !== null && maybePreset !== void 0 ? maybePreset : newDuotone // use preset or fallback to custom colors. } }; setAttributes({ style: newStyle }); }, settings: settings }))); } /** * Filters registered block settings, extending attributes to include * the `duotone` attribute. * * @param {Object} settings Original block settings. * * @return {Object} Filtered block settings. */ function addDuotoneAttributes(settings) { // Previous `color.__experimentalDuotone` support flag is migrated via // block_type_metadata_settings filter in `lib/block-supports/duotone.php`. if (!hasBlockSupport(settings, 'filter.duotone')) { 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; } /** * Override the default edit UI to include toolbar controls for duotone if the * block supports duotone. * * @param {Function} BlockEdit Original component. * * @return {Function} Wrapped component. */ const withDuotoneControls = createHigherOrderComponent(BlockEdit => props => { // Previous `color.__experimentalDuotone` support flag is migrated via // block_type_metadata_settings filter in `lib/block-supports/duotone.php`. const hasDuotoneSupport = hasBlockSupport(props.name, 'filter.duotone'); const blockEditingMode = useBlockEditingMode(); // CAUTION: code added before this line will be executed // for all blocks, not just those that support duotone. Code added // above this line should be carefully evaluated for its impact on // performance. return createElement(Fragment, null, hasDuotoneSupport && blockEditingMode === 'default' && createElement(DuotonePanel, props), createElement(BlockEdit, props)); }, 'withDuotoneControls'); function DuotoneStyles({ id: filterId, selector: duotoneSelector, attribute: duotoneAttr }) { const element = useContext(BlockList.__unstableElementContext); const duotonePalette = useMultiOriginPresets({ presetSetting: 'color.duotone', defaultSetting: 'color.defaultDuotone' }); // Possible values for duotone attribute: // 1. Array of colors - e.g. ['#000000', '#ffffff']. // 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|green-blue' or 'var(--wp--preset--duotone--green-blue)'' // 3. A CSS string - e.g. 'unset' to remove globally applied duotone. const isCustom = Array.isArray(duotoneAttr); const duotonePreset = isCustom ? undefined : getColorsFromDuotonePreset(duotoneAttr, duotonePalette); const isPreset = typeof duotoneAttr === 'string' && duotonePreset; const isCSS = typeof duotoneAttr === 'string' && !isPreset; // Match the structure of WP_Duotone_Gutenberg::render_duotone_support() in PHP. let colors = null; if (isPreset) { // Array of colors. colors = duotonePreset; } else if (isCSS) { // CSS filter property string (e.g. 'unset'). colors = duotoneAttr; } else if (isCustom) { // Array of colors. colors = duotoneAttr; } // Build the CSS selectors to which the filter will be applied. const selectors = duotoneSelector.split(','); const selectorsScoped = selectors.map(selectorPart => { // Extra .editor-styles-wrapper specificity is needed in the editor // since we're not using inline styles to apply the filter. We need to // override duotone applied by global styles and theme.json. // Assuming the selector part is a subclass selector (not a tag name) // so we can prepend the filter id class. If we want to support elements // such as `img` or namespaces, we'll need to add a case for that here. return `.editor-styles-wrapper .${filterId}${selectorPart.trim()}`; }); const selector = selectorsScoped.join(', '); const isValidFilter = Array.isArray(colors) || colors === 'unset'; return element && isValidFilter && createPortal(createElement(InlineDuotone, { selector: selector, id: filterId, colors: colors }), element); } /** * Override the default block element to include duotone styles. * * @param {Function} BlockListBlock Original component. * * @return {Function} Wrapped component. */ const withDuotoneStyles = createHigherOrderComponent(BlockListBlock => props => { const id = useInstanceId(BlockListBlock); const selector = useMemo(() => { const blockType = getBlockType(props.name); if (blockType) { // Backwards compatibility for `supports.color.__experimentalDuotone` // is provided via the `block_type_metadata_settings` filter. If // `supports.filter.duotone` has not been set and the // experimental property has been, the experimental property // value is copied into `supports.filter.duotone`. const duotoneSupport = getBlockSupport(blockType, 'filter.duotone', false); if (!duotoneSupport) { return null; } // If the experimental duotone support was set, that value is // to be treated as a selector and requires scoping. const experimentalDuotone = getBlockSupport(blockType, 'color.__experimentalDuotone', false); if (experimentalDuotone) { const rootSelector = getBlockCSSSelector(blockType); return typeof experimentalDuotone === 'string' ? scopeSelector(rootSelector, experimentalDuotone) : rootSelector; } // Regular filter.duotone support uses filter.duotone selectors with fallbacks. return getBlockCSSSelector(blockType, 'filter.duotone', { fallback: true }); } }, [props.name]); const attribute = props?.attributes?.style?.color?.duotone; const filterClass = `wp-duotone-${id}`; const shouldRender = selector && attribute; const className = shouldRender ? classnames(props?.className, filterClass) : props?.className; // CAUTION: code added before this line will be executed // for all blocks, not just those that support duotone. Code added // above this line should be carefully evaluated for its impact on // performance. return createElement(Fragment, null, shouldRender && createElement(DuotoneStyles, { id: filterClass, selector: selector, attribute: attribute }), createElement(BlockListBlock, _extends({}, props, { className: className }))); }, 'withDuotoneStyles'); addFilter('blocks.registerBlockType', 'core/editor/duotone/add-attributes', addDuotoneAttributes); addFilter('editor.BlockEdit', 'core/editor/duotone/with-editor-controls', withDuotoneControls); addFilter('editor.BlockListBlock', 'core/editor/duotone/with-styles', withDuotoneStyles); //# sourceMappingURL=duotone.js.map