UNPKG

@gechiui/block-editor

Version:
277 lines (253 loc) 9.22 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { createElement, Fragment } from "@gechiui/element"; /** * External dependencies */ import classnames from 'classnames'; import { colord, extend } from 'colord'; import namesPlugin from 'colord/plugins/names'; /** * GeChiUI dependencies */ import { getBlockSupport, hasBlockSupport } from '@gechiui/blocks'; import { SVG } from '@gechiui/components'; import { createHigherOrderComponent, useInstanceId } from '@gechiui/compose'; import { addFilter } from '@gechiui/hooks'; import { useContext, createPortal } from '@gechiui/element'; /** * Internal dependencies */ import { BlockControls, __experimentalDuotoneControl as DuotoneControl, useSetting } from '../components'; import BlockList from '../components/block-list'; const EMPTY_ARRAY = []; extend([namesPlugin]); /** * Convert a list of colors to an object of R, G, and B values. * * @param {string[]} colors Array of RBG color strings. * * @return {Object} R, G, and B values. */ export function getValuesFromColors() { let colors = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; const values = { r: [], g: [], b: [], a: [] }; colors.forEach(color => { const rgbColor = colord(color).toRgb(); values.r.push(rgbColor.r / 255); values.g.push(rgbColor.g / 255); values.b.push(rgbColor.b / 255); values.a.push(rgbColor.a); }); return values; } /** * Values for the SVG `feComponentTransfer`. * * @typedef Values {Object} * @property {number[]} r Red values. * @property {number[]} g Green values. * @property {number[]} b Blue values. * @property {number[]} a Alpha values. */ /** * 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 {Values} props.values R, G, B, and A values to filter with. * * @return {GCElement} Duotone element. */ function DuotoneFilter(_ref) { let { selector, id, values } = _ref; const stylesheet = ` ${selector} { filter: url( #${id} ); } `; return createElement(Fragment, null, createElement(SVG, { xmlnsXlink: "http://www.w3.org/1999/xlink", viewBox: "0 0 0 0", width: "0", height: "0", focusable: "false", role: "none", style: { visibility: 'hidden', position: 'absolute', left: '-9999px', overflow: 'hidden' } }, createElement("defs", null, createElement("filter", { id: id }, createElement("feColorMatrix", { // Use sRGB instead of linearRGB so transparency looks correct. colorInterpolationFilters: "sRGB", type: "matrix" // Use perceptual brightness to convert to grayscale. , values: " .299 .587 .114 0 0 .299 .587 .114 0 0 .299 .587 .114 0 0 .299 .587 .114 0 0 " }), createElement("feComponentTransfer", { // Use sRGB instead of linearRGB to be consistent with how CSS gradients work. colorInterpolationFilters: "sRGB" }, createElement("feFuncR", { type: "table", tableValues: values.r.join(' ') }), createElement("feFuncG", { type: "table", tableValues: values.g.join(' ') }), createElement("feFuncB", { type: "table", tableValues: values.b.join(' ') }), createElement("feFuncA", { type: "table", tableValues: values.a.join(' ') })), createElement("feComposite", { // Re-mask the image with the original transparency since the feColorMatrix above loses that information. in2: "SourceGraphic", operator: "in" })))), createElement("style", { dangerouslySetInnerHTML: { __html: stylesheet } })); } function DuotonePanel(_ref2) { var _style$color; let { attributes, setAttributes } = _ref2; const style = attributes === null || attributes === void 0 ? void 0 : attributes.style; const duotone = style === null || style === void 0 ? void 0 : (_style$color = style.color) === null || _style$color === void 0 ? void 0 : _style$color.duotone; const duotonePalette = useSetting('color.duotone') || EMPTY_ARRAY; const colorPalette = useSetting('color.palette') || EMPTY_ARRAY; const disableCustomColors = !useSetting('color.custom'); const disableCustomDuotone = !useSetting('color.customDuotone') || (colorPalette === null || colorPalette === void 0 ? void 0 : colorPalette.length) === 0 && disableCustomColors; if ((duotonePalette === null || duotonePalette === void 0 ? void 0 : duotonePalette.length) === 0 && disableCustomDuotone) { return null; } return createElement(BlockControls, { group: "block", __experimentalShareWithChildBlocks: true }, createElement(DuotoneControl, { duotonePalette: duotonePalette, colorPalette: colorPalette, disableCustomDuotone: disableCustomDuotone, disableCustomColors: disableCustomColors, value: duotone, onChange: newDuotone => { const newStyle = { ...style, color: { ...(style === null || style === void 0 ? void 0 : style.color), duotone: newDuotone } }; setAttributes({ style: newStyle }); } })); } /** * 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) { if (!hasBlockSupport(settings, 'color.__experimentalDuotone')) { 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 => { const hasDuotoneSupport = hasBlockSupport(props.name, 'color.__experimentalDuotone'); return createElement(Fragment, null, createElement(BlockEdit, props), hasDuotoneSupport && createElement(DuotonePanel, props)); }, 'withDuotoneControls'); /** * Function that scopes a selector with another one. This works a bit like * SCSS nesting except the `&` operator isn't supported. * * @example * ```js * const scope = '.a, .b .c'; * const selector = '> .x, .y'; * const merged = scopeSelector( scope, selector ); * // merged is '.a > .x, .a .y, .b .c > .x, .b .c .y' * ``` * * @param {string} scope Selector to scope to. * @param {string} selector Original selector. * * @return {string} Scoped selector. */ function scopeSelector(scope, selector) { const scopes = scope.split(','); const selectors = selector.split(','); const selectorsScoped = []; scopes.forEach(outer => { selectors.forEach(inner => { selectorsScoped.push(`${outer.trim()} ${inner.trim()}`); }); }); return selectorsScoped.join(', '); } /** * Override the default block element to include duotone styles. * * @param {Function} BlockListBlock Original component. * * @return {Function} Wrapped component. */ const withDuotoneStyles = createHigherOrderComponent(BlockListBlock => props => { var _props$attributes, _props$attributes$sty, _props$attributes$sty2; const duotoneSupport = getBlockSupport(props.name, 'color.__experimentalDuotone'); const values = props === null || props === void 0 ? void 0 : (_props$attributes = props.attributes) === null || _props$attributes === void 0 ? void 0 : (_props$attributes$sty = _props$attributes.style) === null || _props$attributes$sty === void 0 ? void 0 : (_props$attributes$sty2 = _props$attributes$sty.color) === null || _props$attributes$sty2 === void 0 ? void 0 : _props$attributes$sty2.duotone; if (!duotoneSupport || !values) { return createElement(BlockListBlock, props); } const id = `gc-duotone-${useInstanceId(BlockListBlock)}`; // 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. const selectorsGroup = scopeSelector(`.editor-styles-wrapper .${id}`, duotoneSupport); const className = classnames(props === null || props === void 0 ? void 0 : props.className, id); const element = useContext(BlockList.__unstableElementContext); return createElement(Fragment, null, element && createPortal(createElement(DuotoneFilter, { selector: selectorsGroup, id: id, values: getValuesFromColors(values) }), element), 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