@gechiui/block-editor
Version:
296 lines (258 loc) • 10 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getValuesFromColors = getValuesFromColors;
var _element = require("@gechiui/element");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _classnames = _interopRequireDefault(require("classnames"));
var _colord = require("colord");
var _names = _interopRequireDefault(require("colord/plugins/names"));
var _blocks = require("@gechiui/blocks");
var _components = require("@gechiui/components");
var _compose = require("@gechiui/compose");
var _hooks = require("@gechiui/hooks");
var _components2 = require("../components");
var _blockList = _interopRequireDefault(require("../components/block-list"));
/**
* External dependencies
*/
/**
* GeChiUI dependencies
*/
/**
* Internal dependencies
*/
const EMPTY_ARRAY = [];
(0, _colord.extend)([_names.default]);
/**
* 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.
*/
function getValuesFromColors() {
let colors = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
const values = {
r: [],
g: [],
b: [],
a: []
};
colors.forEach(color => {
const rgbColor = (0, _colord.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 (0, _element.createElement)(_element.Fragment, null, (0, _element.createElement)(_components.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'
}
}, (0, _element.createElement)("defs", null, (0, _element.createElement)("filter", {
id: id
}, (0, _element.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 "
}), (0, _element.createElement)("feComponentTransfer", {
// Use sRGB instead of linearRGB to be consistent with how CSS gradients work.
colorInterpolationFilters: "sRGB"
}, (0, _element.createElement)("feFuncR", {
type: "table",
tableValues: values.r.join(' ')
}), (0, _element.createElement)("feFuncG", {
type: "table",
tableValues: values.g.join(' ')
}), (0, _element.createElement)("feFuncB", {
type: "table",
tableValues: values.b.join(' ')
}), (0, _element.createElement)("feFuncA", {
type: "table",
tableValues: values.a.join(' ')
})), (0, _element.createElement)("feComposite", {
// Re-mask the image with the original transparency since the feColorMatrix above loses that information.
in2: "SourceGraphic",
operator: "in"
})))), (0, _element.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 = (0, _components2.useSetting)('color.duotone') || EMPTY_ARRAY;
const colorPalette = (0, _components2.useSetting)('color.palette') || EMPTY_ARRAY;
const disableCustomColors = !(0, _components2.useSetting)('color.custom');
const disableCustomDuotone = !(0, _components2.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 (0, _element.createElement)(_components2.BlockControls, {
group: "block",
__experimentalShareWithChildBlocks: true
}, (0, _element.createElement)(_components2.__experimentalDuotoneControl, {
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 (!(0, _blocks.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 = (0, _compose.createHigherOrderComponent)(BlockEdit => props => {
const hasDuotoneSupport = (0, _blocks.hasBlockSupport)(props.name, 'color.__experimentalDuotone');
return (0, _element.createElement)(_element.Fragment, null, (0, _element.createElement)(BlockEdit, props), hasDuotoneSupport && (0, _element.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 = (0, _compose.createHigherOrderComponent)(BlockListBlock => props => {
var _props$attributes, _props$attributes$sty, _props$attributes$sty2;
const duotoneSupport = (0, _blocks.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 (0, _element.createElement)(BlockListBlock, props);
}
const id = `gc-duotone-${(0, _compose.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 = (0, _classnames.default)(props === null || props === void 0 ? void 0 : props.className, id);
const element = (0, _element.useContext)(_blockList.default.__unstableElementContext);
return (0, _element.createElement)(_element.Fragment, null, element && (0, _element.createPortal)((0, _element.createElement)(DuotoneFilter, {
selector: selectorsGroup,
id: id,
values: getValuesFromColors(values)
}), element), (0, _element.createElement)(BlockListBlock, (0, _extends2.default)({}, props, {
className: className
})));
}, 'withDuotoneStyles');
(0, _hooks.addFilter)('blocks.registerBlockType', 'core/editor/duotone/add-attributes', addDuotoneAttributes);
(0, _hooks.addFilter)('editor.BlockEdit', 'core/editor/duotone/with-editor-controls', withDuotoneControls);
(0, _hooks.addFilter)('editor.BlockListBlock', 'core/editor/duotone/with-styles', withDuotoneStyles);
//# sourceMappingURL=duotone.js.map