@gechiui/block-editor
Version:
277 lines (253 loc) • 9.22 kB
JavaScript
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