UNPKG

@wordpress/block-editor

Version:
302 lines (290 loc) 11.5 kB
/** * External dependencies */ import { extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; /** * WordPress dependencies */ import { getBlockSupport, getBlockType, hasBlockSupport } from '@wordpress/blocks'; import { useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { useMemo, useEffect } from '@wordpress/element'; /** * Internal dependencies */ import { BlockControls, InspectorControls, __experimentalDuotoneControl as DuotoneControl, useSettings } from '../components'; import { getDuotoneFilter, getDuotoneStylesheet, getDuotoneUnsetStylesheet } from '../components/duotone/utils'; import { getBlockCSSSelector } from '../components/global-styles/get-block-css-selector'; import { scopeSelector } from '../components/global-styles/utils'; import { useBlockSettings, usePrivateStyleOverride } from './utils'; import { default as StylesFiltersPanel } from '../components/global-styles/filters-panel'; import { useBlockEditingMode } from '../components/block-editing-mode'; import { useBlockElement } from '../components/block-list/use-block-props/use-block-refs'; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const EMPTY_ARRAY = []; // Safari does not always update the duotone filter when the duotone colors // are changed. This browser check is later used to force a re-render of the block // element to ensure the duotone filter is updated. The check is included at the // root of this file as it only needs to be run once per page load. const isSafari = window?.navigator.userAgent && window.navigator.userAgent.includes('Safari') && !window.navigator.userAgent.includes('Chrome') && !window.navigator.userAgent.includes('Chromium'); extend([namesPlugin]); function useMultiOriginPresets({ presetSetting, defaultSetting }) { const [enableDefault, userPresets, themePresets, defaultPresets] = useSettings(defaultSetting, `${presetSetting}.custom`, `${presetSetting}.theme`, `${presetSetting}.default`); return useMemo(() => [...(userPresets || EMPTY_ARRAY), ...(themePresets || EMPTY_ARRAY), ...(enableDefault && defaultPresets || EMPTY_ARRAY)], [enableDefault, 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 DuotonePanelPure({ style, setAttributes, name }) { const duotoneStyle = style?.color?.duotone; const settings = useBlockSettings(name); const blockEditingMode = useBlockEditingMode(); const duotonePalette = useMultiOriginPresets({ presetSetting: 'color.duotone', defaultSetting: 'color.defaultDuotone' }); const colorPalette = useMultiOriginPresets({ presetSetting: 'color.palette', defaultSetting: 'color.defaultPalette' }); const [enableCustomColors, enableCustomDuotone] = useSettings('color.custom', 'color.customDuotone'); const disableCustomColors = !enableCustomColors; const disableCustomDuotone = !enableCustomDuotone || colorPalette?.length === 0 && disableCustomColors; if (duotonePalette?.length === 0 && disableCustomDuotone) { return null; } if (blockEditingMode !== 'default') { return null; } const duotonePresetOrColors = !Array.isArray(duotoneStyle) ? getColorsFromDuotonePreset(duotoneStyle, duotonePalette) : duotoneStyle; return /*#__PURE__*/_jsxs(_Fragment, { children: [/*#__PURE__*/_jsx(InspectorControls, { group: "filter", children: /*#__PURE__*/_jsx(StylesFiltersPanel, { value: { filter: { duotone: duotonePresetOrColors } }, onChange: newDuotone => { const newStyle = { ...style, color: { ...newDuotone?.filter } }; setAttributes({ style: newStyle }); }, settings: settings }) }), /*#__PURE__*/_jsx(BlockControls, { group: "block", __experimentalShareWithChildBlocks: true, children: /*#__PURE__*/_jsx(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 }) })] }); } export default { shareWithChildBlocks: true, edit: DuotonePanelPure, useBlockProps, attributeKeys: ['style'], hasSupport(name) { return hasBlockSupport(name, 'filter.duotone'); } }; /** * 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; } function useDuotoneStyles({ clientId, id: filterId, selector: duotoneSelector, attribute: duotoneAttr }) { 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 => { // 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 `.${filterId}${selectorPart.trim()}`; }); const selector = selectorsScoped.join(', '); const isValidFilter = Array.isArray(colors) || colors === 'unset'; usePrivateStyleOverride(isValidFilter ? { css: colors !== 'unset' ? getDuotoneStylesheet(selector, filterId) : getDuotoneUnsetStylesheet(selector), __unstableType: 'presets' } : undefined); usePrivateStyleOverride(isValidFilter ? { assets: colors !== 'unset' ? getDuotoneFilter(filterId, colors) : '', __unstableType: 'svgs' } : undefined); const blockElement = useBlockElement(clientId); useEffect(() => { if (!isValidFilter) { return; } // Safari does not always update the duotone filter when the duotone // colors are changed. When using Safari, force the block element to be // repainted by the browser to ensure any changes are reflected // visually. This logic matches that used on the site frontend in // `block-supports/duotone.php`. if (blockElement && isSafari) { const display = blockElement.style.display; // Switch to `inline-block` to force a repaint. In the editor, // `inline-block` is used instead of `none` to ensure that scroll // position is not affected, as `none` results in the editor // scrolling to the top of the block. blockElement.style.setProperty('display', 'inline-block'); // Simply accessing el.offsetHeight flushes layout and style changes // in WebKit without having to wait for setTimeout. // eslint-disable-next-line no-unused-expressions blockElement.offsetHeight; blockElement.style.setProperty('display', display); } // `colors` must be a dependency so this effect runs when the colors // change in Safari. }, [isValidFilter, blockElement, colors]); } // Used for generating the instance ID const DUOTONE_BLOCK_PROPS_REFERENCE = {}; function useBlockProps({ clientId, name, style }) { const id = useInstanceId(DUOTONE_BLOCK_PROPS_REFERENCE); const selector = useMemo(() => { const blockType = getBlockType(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 }); } }, [name]); const attribute = style?.color?.duotone; const filterClass = `wp-duotone-${id}`; const shouldRender = selector && attribute; useDuotoneStyles({ clientId, id: filterClass, selector, attribute }); return { className: shouldRender ? filterClass : '' }; } addFilter('blocks.registerBlockType', 'core/editor/duotone/add-attributes', addDuotoneAttributes); //# sourceMappingURL=duotone.js.map