@wordpress/blocks
Version:
Block API for WordPress.
383 lines (359 loc) • 13.8 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.__experimentalGetBlockAttributesNamesByRole = void 0;
exports.__experimentalSanitizeBlockAttributes = __experimentalSanitizeBlockAttributes;
exports.getAccessibleBlockLabel = getAccessibleBlockLabel;
exports.getBlockAttributesNamesByRole = getBlockAttributesNamesByRole;
exports.getBlockLabel = getBlockLabel;
exports.getDefault = getDefault;
exports.isBlockRegistered = isBlockRegistered;
exports.isContentBlock = isContentBlock;
exports.isUnmodifiedBlock = isUnmodifiedBlock;
exports.isUnmodifiedDefaultBlock = isUnmodifiedDefaultBlock;
exports.isValidIcon = isValidIcon;
exports.normalizeBlockType = normalizeBlockType;
exports.normalizeIconObject = normalizeIconObject;
exports.omit = omit;
var _colord = require("colord");
var _names = _interopRequireDefault(require("colord/plugins/names"));
var _a11y = _interopRequireDefault(require("colord/plugins/a11y"));
var _element = require("@wordpress/element");
var _i18n = require("@wordpress/i18n");
var _dom = require("@wordpress/dom");
var _richText = require("@wordpress/rich-text");
var _deprecated = _interopRequireDefault(require("@wordpress/deprecated"));
var _constants = require("./constants");
var _registration = require("./registration");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
(0, _colord.extend)([_names.default, _a11y.default]);
/**
* Array of icon colors containing a color to be used if the icon color
* was not explicitly set but the icon background color was.
*
* @type {Object}
*/
const ICON_COLORS = ['#191e23', '#f8f9f9'];
/**
* Determines whether the block's attributes are equal to the default attributes
* which means the block is unmodified.
*
* @param {WPBlock} block Block Object.
* @param {?string} role Optional role to filter attributes for modification check.
*
* @return {boolean} Whether the block is an unmodified block.
*/
function isUnmodifiedBlock(block, role) {
var _getBlockType$attribu;
const blockAttributes = (_getBlockType$attribu = (0, _registration.getBlockType)(block.name)?.attributes) !== null && _getBlockType$attribu !== void 0 ? _getBlockType$attribu : {};
// Filter attributes by role if a role is provided.
const attributesToCheck = role ? Object.entries(blockAttributes).filter(([key, definition]) => {
// A special case for the metadata attribute.
// It can include block bindings that serve as a source of content,
// without directly modifying content attributes.
if (role === 'content' && key === 'metadata') {
return true;
}
return definition.role === role || definition.__experimentalRole === role;
}) : Object.entries(blockAttributes);
return attributesToCheck.every(([key, definition]) => {
const value = block.attributes[key];
// Every attribute that has a default must match the default.
if (definition.hasOwnProperty('default')) {
return value === definition.default;
}
// The rich text type is a bit different from the rest because it
// has an implicit default value of an empty RichTextData instance,
// so check the length of the value.
if (definition.type === 'rich-text') {
return !value?.length;
}
// Every attribute that doesn't have a default should be undefined.
return value === undefined;
});
}
/**
* Determines whether the block is a default block and its attributes are equal
* to the default attributes which means the block is unmodified.
*
* @param {WPBlock} block Block Object
* @param {?string} role Optional role to filter attributes for modification check.
*
* @return {boolean} Whether the block is an unmodified default block.
*/
function isUnmodifiedDefaultBlock(block, role) {
return block.name === (0, _registration.getDefaultBlockName)() && isUnmodifiedBlock(block, role);
}
/**
* Function that checks if the parameter is a valid icon.
*
* @param {*} icon Parameter to be checked.
*
* @return {boolean} True if the parameter is a valid icon and false otherwise.
*/
function isValidIcon(icon) {
return !!icon && (typeof icon === 'string' || (0, _element.isValidElement)(icon) || typeof icon === 'function' || icon instanceof _element.Component);
}
/**
* Function that receives an icon as set by the blocks during the registration
* and returns a new icon object that is normalized so we can rely on just on possible icon structure
* in the codebase.
*
* @param {WPBlockTypeIconRender} icon Render behavior of a block type icon;
* one of a Dashicon slug, an element, or a
* component.
*
* @return {WPBlockTypeIconDescriptor} Object describing the icon.
*/
function normalizeIconObject(icon) {
icon = icon || _constants.BLOCK_ICON_DEFAULT;
if (isValidIcon(icon)) {
return {
src: icon
};
}
if ('background' in icon) {
const colordBgColor = (0, _colord.colord)(icon.background);
const getColorContrast = iconColor => colordBgColor.contrast(iconColor);
const maxContrast = Math.max(...ICON_COLORS.map(getColorContrast));
return {
...icon,
foreground: icon.foreground ? icon.foreground : ICON_COLORS.find(iconColor => getColorContrast(iconColor) === maxContrast),
shadowColor: colordBgColor.alpha(0.3).toRgbString()
};
}
return icon;
}
/**
* Normalizes block type passed as param. When string is passed then
* it converts it to the matching block type object.
* It passes the original object otherwise.
*
* @param {string|Object} blockTypeOrName Block type or name.
*
* @return {?Object} Block type.
*/
function normalizeBlockType(blockTypeOrName) {
if (typeof blockTypeOrName === 'string') {
return (0, _registration.getBlockType)(blockTypeOrName);
}
return blockTypeOrName;
}
/**
* Get the label for the block, usually this is either the block title,
* or the value of the block's `label` function when that's specified.
*
* @param {Object} blockType The block type.
* @param {Object} attributes The values of the block's attributes.
* @param {Object} context The intended use for the label.
*
* @return {string} The block label.
*/
function getBlockLabel(blockType, attributes, context = 'visual') {
const {
__experimentalLabel: getLabel,
title
} = blockType;
const label = getLabel && getLabel(attributes, {
context
});
if (!label) {
return title;
}
if (label.toPlainText) {
return label.toPlainText();
}
// Strip any HTML (i.e. RichText formatting) before returning.
return (0, _dom.__unstableStripHTML)(label);
}
/**
* Get a label for the block for use by screenreaders, this is more descriptive
* than the visual label and includes the block title and the value of the
* `getLabel` function if it's specified.
*
* @param {?Object} blockType The block type.
* @param {Object} attributes The values of the block's attributes.
* @param {?number} position The position of the block in the block list.
* @param {string} [direction='vertical'] The direction of the block layout.
*
* @return {string} The block label.
*/
function getAccessibleBlockLabel(blockType, attributes, position, direction = 'vertical') {
// `title` is already localized, `label` is a user-supplied value.
const title = blockType?.title;
const label = blockType ? getBlockLabel(blockType, attributes, 'accessibility') : '';
const hasPosition = position !== undefined;
// getBlockLabel returns the block title as a fallback when there's no label,
// if it did return the title, this function needs to avoid adding the
// title twice within the accessible label. Use this `hasLabel` boolean to
// handle that.
const hasLabel = label && label !== title;
if (hasPosition && direction === 'vertical') {
if (hasLabel) {
return (0, _i18n.sprintf)(/* translators: accessibility text. 1: The block title. 2: The block row number. 3: The block label.. */
(0, _i18n.__)('%1$s Block. Row %2$d. %3$s'), title, position, label);
}
return (0, _i18n.sprintf)(/* translators: accessibility text. 1: The block title. 2: The block row number. */
(0, _i18n.__)('%1$s Block. Row %2$d'), title, position);
} else if (hasPosition && direction === 'horizontal') {
if (hasLabel) {
return (0, _i18n.sprintf)(/* translators: accessibility text. 1: The block title. 2: The block column number. 3: The block label.. */
(0, _i18n.__)('%1$s Block. Column %2$d. %3$s'), title, position, label);
}
return (0, _i18n.sprintf)(/* translators: accessibility text. 1: The block title. 2: The block column number. */
(0, _i18n.__)('%1$s Block. Column %2$d'), title, position);
}
if (hasLabel) {
return (0, _i18n.sprintf)(/* translators: accessibility text. 1: The block title. 2: The block label. */
(0, _i18n.__)('%1$s Block. %2$s'), title, label);
}
return (0, _i18n.sprintf)(/* translators: accessibility text. %s: The block title. */
(0, _i18n.__)('%s Block'), title);
}
function getDefault(attributeSchema) {
if (attributeSchema.default !== undefined) {
return attributeSchema.default;
}
if (attributeSchema.type === 'rich-text') {
return new _richText.RichTextData();
}
}
/**
* Check if a block is registered.
*
* @param {string} name The block's name.
*
* @return {boolean} Whether the block is registered.
*/
function isBlockRegistered(name) {
return (0, _registration.getBlockType)(name) !== undefined;
}
/**
* Ensure attributes contains only values defined by block type, and merge
* default values for missing attributes.
*
* @param {string} name The block's name.
* @param {Object} attributes The block's attributes.
* @return {Object} The sanitized attributes.
*/
function __experimentalSanitizeBlockAttributes(name, attributes) {
// Get the type definition associated with a registered block.
const blockType = (0, _registration.getBlockType)(name);
if (undefined === blockType) {
throw new Error(`Block type '${name}' is not registered.`);
}
return Object.entries(blockType.attributes).reduce((accumulator, [key, schema]) => {
const value = attributes[key];
if (undefined !== value) {
if (schema.type === 'rich-text') {
if (value instanceof _richText.RichTextData) {
accumulator[key] = value;
} else if (typeof value === 'string') {
accumulator[key] = _richText.RichTextData.fromHTMLString(value);
}
} else if (schema.type === 'string' && value instanceof _richText.RichTextData) {
accumulator[key] = value.toHTMLString();
} else {
accumulator[key] = value;
}
} else {
const _default = getDefault(schema);
if (undefined !== _default) {
accumulator[key] = _default;
}
}
if (['node', 'children'].indexOf(schema.source) !== -1) {
// Ensure value passed is always an array, which we're expecting in
// the RichText component to handle the deprecated value.
if (typeof accumulator[key] === 'string') {
accumulator[key] = [accumulator[key]];
} else if (!Array.isArray(accumulator[key])) {
accumulator[key] = [];
}
}
return accumulator;
}, {});
}
/**
* Filter block attributes by `role` and return their names.
*
* @param {string} name Block attribute's name.
* @param {string} role The role of a block attribute.
*
* @return {string[]} The attribute names that have the provided role.
*/
function getBlockAttributesNamesByRole(name, role) {
const attributes = (0, _registration.getBlockType)(name)?.attributes;
if (!attributes) {
return [];
}
const attributesNames = Object.keys(attributes);
if (!role) {
return attributesNames;
}
return attributesNames.filter(attributeName => {
const attribute = attributes[attributeName];
if (attribute?.role === role) {
return true;
}
if (attribute?.__experimentalRole === role) {
(0, _deprecated.default)('__experimentalRole attribute', {
since: '6.7',
version: '6.8',
alternative: 'role attribute',
hint: `Check the block.json of the ${name} block.`
});
return true;
}
return false;
});
}
const __experimentalGetBlockAttributesNamesByRole = (...args) => {
(0, _deprecated.default)('__experimentalGetBlockAttributesNamesByRole', {
since: '6.7',
version: '6.8',
alternative: 'getBlockAttributesNamesByRole'
});
return getBlockAttributesNamesByRole(...args);
};
/**
* Checks if a block is a content block by examining its attributes.
* A block is considered a content block if it has at least one attribute
* with a role of 'content'.
*
* @param {string} name The name of the block to check.
* @return {boolean} Whether the block is a content block.
*/
exports.__experimentalGetBlockAttributesNamesByRole = __experimentalGetBlockAttributesNamesByRole;
function isContentBlock(name) {
const attributes = (0, _registration.getBlockType)(name)?.attributes;
if (!attributes) {
return false;
}
return !!Object.keys(attributes)?.some(attributeKey => {
const attribute = attributes[attributeKey];
return attribute?.role === 'content' || attribute?.__experimentalRole === 'content';
});
}
/**
* Return a new object with the specified keys omitted.
*
* @param {Object} object Original object.
* @param {Array} keys Keys to be omitted.
*
* @return {Object} Object with omitted keys.
*/
function omit(object, keys) {
return Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key)));
}
//# sourceMappingURL=utils.js.map