@wordpress/block-library
Version:
Block library for the WordPress editor.
479 lines (465 loc) • 18.4 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = _interopRequireDefault(require("clsx"));
var _coreData = require("@wordpress/core-data");
var _element = require("@wordpress/element");
var _components = require("@wordpress/components");
var _compose = require("@wordpress/compose");
var _blockEditor = require("@wordpress/block-editor");
var _i18n = require("@wordpress/i18n");
var _data = require("@wordpress/data");
var _blob = require("@wordpress/blob");
var _notices = require("@wordpress/notices");
var _shared = require("../shared");
var _inspectorControls = _interopRequireDefault(require("./inspector-controls"));
var _blockControls = _interopRequireDefault(require("./block-controls"));
var _coverPlaceholder = _interopRequireDefault(require("./cover-placeholder"));
var _resizableCoverPopover = _interopRequireDefault(require("./resizable-cover-popover"));
var _colorUtils = require("./color-utils");
var _constants = require("../constants");
var _jsxRuntime = require("react/jsx-runtime");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
function getInnerBlocksTemplate(attributes) {
return [['core/paragraph', {
align: 'center',
placeholder: (0, _i18n.__)('Write title…'),
...attributes
}]];
}
/**
* Is the URL a temporary blob URL? A blob URL is one that is used temporarily while
* the media (image or video) is being uploaded and will not have an id allocated yet.
*
* @param {number} id The id of the media.
* @param {string} url The url of the media.
*
* @return {boolean} Is the URL a Blob URL.
*/
const isTemporaryMedia = (id, url) => !id && (0, _blob.isBlobURL)(url);
function CoverEdit({
attributes,
clientId,
isSelected,
overlayColor,
setAttributes,
setOverlayColor,
toggleSelection,
context: {
postId,
postType
}
}) {
var _media$media_details$;
const {
contentPosition,
id,
url: originalUrl,
backgroundType: originalBackgroundType,
useFeaturedImage,
dimRatio,
focalPoint,
hasParallax,
isDark,
isRepeated,
minHeight,
minHeightUnit,
alt,
allowedBlocks,
templateLock,
tagName: TagName = 'div',
isUserOverlayColor,
sizeSlug
} = attributes;
const [featuredImage] = (0, _coreData.useEntityProp)('postType', postType, 'featured_media', postId);
const {
getSettings
} = (0, _data.useSelect)(_blockEditor.store);
const {
__unstableMarkNextChangeAsNotPersistent
} = (0, _data.useDispatch)(_blockEditor.store);
const {
media
} = (0, _data.useSelect)(select => {
return {
media: featuredImage && useFeaturedImage ? select(_coreData.store).getMedia(featuredImage, {
context: 'view'
}) : undefined
};
}, [featuredImage, useFeaturedImage]);
const mediaUrl = (_media$media_details$ = media?.media_details?.sizes?.[sizeSlug]?.source_url) !== null && _media$media_details$ !== void 0 ? _media$media_details$ : media?.source_url;
// User can change the featured image outside of the block, but we still
// need to update the block when that happens. This effect should only
// run when the featured image changes in that case. All other cases are
// handled in their respective callbacks.
(0, _element.useEffect)(() => {
(async () => {
if (!useFeaturedImage) {
return;
}
const averageBackgroundColor = await (0, _colorUtils.getMediaColor)(mediaUrl);
let newOverlayColor = overlayColor.color;
if (!isUserOverlayColor) {
newOverlayColor = averageBackgroundColor;
__unstableMarkNextChangeAsNotPersistent();
setOverlayColor(newOverlayColor);
}
const newIsDark = (0, _colorUtils.compositeIsDark)(dimRatio, newOverlayColor, averageBackgroundColor);
__unstableMarkNextChangeAsNotPersistent();
setAttributes({
isDark: newIsDark,
isUserOverlayColor: isUserOverlayColor || false
});
})();
// Update the block only when the featured image changes.
}, [mediaUrl]);
// instead of destructuring the attributes
// we define the url and background type
// depending on the value of the useFeaturedImage flag
// to preview in edit the dynamic featured image
const url = useFeaturedImage ? mediaUrl :
// Ensure the url is not malformed due to sanitization through `wp_kses`.
originalUrl?.replaceAll('&', '&');
const backgroundType = useFeaturedImage ? _shared.IMAGE_BACKGROUND_TYPE : originalBackgroundType;
const {
createErrorNotice
} = (0, _data.useDispatch)(_notices.store);
const {
gradientClass,
gradientValue
} = (0, _blockEditor.__experimentalUseGradient)();
const onSelectMedia = async newMedia => {
const mediaAttributes = (0, _shared.attributesFromMedia)(newMedia);
const isImage = [newMedia?.type, newMedia?.media_type].includes(_shared.IMAGE_BACKGROUND_TYPE);
const averageBackgroundColor = await (0, _colorUtils.getMediaColor)(isImage ? newMedia?.url : undefined);
let newOverlayColor = overlayColor.color;
if (!isUserOverlayColor) {
newOverlayColor = averageBackgroundColor;
setOverlayColor(newOverlayColor);
// Make undo revert the next setAttributes and the previous setOverlayColor.
__unstableMarkNextChangeAsNotPersistent();
}
// Only set a new dimRatio if there was no previous media selected
// to avoid resetting to 50 if it has been explicitly set to 100.
// See issue #52835 for context.
const newDimRatio = originalUrl === undefined && dimRatio === 100 ? 50 : dimRatio;
const newIsDark = (0, _colorUtils.compositeIsDark)(newDimRatio, newOverlayColor, averageBackgroundColor);
if (backgroundType === _shared.IMAGE_BACKGROUND_TYPE && mediaAttributes?.id) {
const {
imageDefaultSize
} = getSettings();
// Try to use the previous selected image size if it's available
// otherwise try the default image size or fallback to full size.
if (sizeSlug && (newMedia?.sizes?.[sizeSlug] || newMedia?.media_details?.sizes?.[sizeSlug])) {
mediaAttributes.sizeSlug = sizeSlug;
mediaAttributes.url = newMedia?.sizes?.[sizeSlug]?.url || newMedia?.media_details?.sizes?.[sizeSlug]?.source_url;
} else if (newMedia?.sizes?.[imageDefaultSize] || newMedia?.media_details?.sizes?.[imageDefaultSize]) {
mediaAttributes.sizeSlug = imageDefaultSize;
mediaAttributes.url = newMedia?.sizes?.[imageDefaultSize]?.url || newMedia?.media_details?.sizes?.[imageDefaultSize]?.source_url;
} else {
mediaAttributes.sizeSlug = _constants.DEFAULT_MEDIA_SIZE_SLUG;
}
}
setAttributes({
...mediaAttributes,
focalPoint: undefined,
useFeaturedImage: undefined,
dimRatio: newDimRatio,
isDark: newIsDark,
isUserOverlayColor: isUserOverlayColor || false
});
};
const onClearMedia = () => {
let newOverlayColor = overlayColor.color;
if (!isUserOverlayColor) {
newOverlayColor = _colorUtils.DEFAULT_OVERLAY_COLOR;
setOverlayColor(undefined);
// Make undo revert the next setAttributes and the previous setOverlayColor.
__unstableMarkNextChangeAsNotPersistent();
}
const newIsDark = (0, _colorUtils.compositeIsDark)(dimRatio, newOverlayColor, _colorUtils.DEFAULT_BACKGROUND_COLOR);
setAttributes({
url: undefined,
id: undefined,
backgroundType: undefined,
focalPoint: undefined,
hasParallax: undefined,
isRepeated: undefined,
useFeaturedImage: undefined,
isDark: newIsDark
});
};
const onSetOverlayColor = async newOverlayColor => {
const averageBackgroundColor = await (0, _colorUtils.getMediaColor)(url);
const newIsDark = (0, _colorUtils.compositeIsDark)(dimRatio, newOverlayColor, averageBackgroundColor);
setOverlayColor(newOverlayColor);
// Make undo revert the next setAttributes and the previous setOverlayColor.
__unstableMarkNextChangeAsNotPersistent();
setAttributes({
isUserOverlayColor: true,
isDark: newIsDark
});
};
const onUpdateDimRatio = async newDimRatio => {
const averageBackgroundColor = await (0, _colorUtils.getMediaColor)(url);
const newIsDark = (0, _colorUtils.compositeIsDark)(newDimRatio, overlayColor.color, averageBackgroundColor);
setAttributes({
dimRatio: newDimRatio,
isDark: newIsDark
});
};
const onUploadError = message => {
createErrorNotice(message, {
type: 'snackbar'
});
};
const isUploadingMedia = isTemporaryMedia(id, url);
const isImageBackground = _shared.IMAGE_BACKGROUND_TYPE === backgroundType;
const isVideoBackground = _shared.VIDEO_BACKGROUND_TYPE === backgroundType;
const blockEditingMode = (0, _blockEditor.useBlockEditingMode)();
const hasNonContentControls = blockEditingMode === 'default';
const [resizeListener, {
height,
width
}] = (0, _compose.useResizeObserver)();
const resizableBoxDimensions = (0, _element.useMemo)(() => {
return {
height: minHeightUnit === 'px' ? minHeight : 'auto',
width: 'auto'
};
}, [minHeight, minHeightUnit]);
const minHeightWithUnit = minHeight && minHeightUnit ? `${minHeight}${minHeightUnit}` : minHeight;
const isImgElement = !(hasParallax || isRepeated);
const style = {
minHeight: minHeightWithUnit || undefined
};
const backgroundImage = url ? `url(${url})` : undefined;
const backgroundPosition = (0, _shared.mediaPosition)(focalPoint);
const bgStyle = {
backgroundColor: overlayColor.color
};
const mediaStyle = {
objectPosition: focalPoint && isImgElement ? (0, _shared.mediaPosition)(focalPoint) : undefined
};
const hasBackground = !!(url || overlayColor.color || gradientValue);
const hasInnerBlocks = (0, _data.useSelect)(select => select(_blockEditor.store).getBlock(clientId).innerBlocks.length > 0, [clientId]);
const ref = (0, _element.useRef)();
const blockProps = (0, _blockEditor.useBlockProps)({
ref
});
// Check for fontSize support before we pass a fontSize attribute to the innerBlocks.
const [fontSizes] = (0, _blockEditor.useSettings)('typography.fontSizes');
const hasFontSizes = fontSizes?.length > 0;
const innerBlocksTemplate = getInnerBlocksTemplate({
fontSize: hasFontSizes ? 'large' : undefined
});
const innerBlocksProps = (0, _blockEditor.useInnerBlocksProps)({
className: 'wp-block-cover__inner-container'
}, {
// Avoid template sync when the `templateLock` value is `all` or `contentOnly`.
// See: https://github.com/WordPress/gutenberg/pull/45632
template: !hasInnerBlocks ? innerBlocksTemplate : undefined,
templateInsertUpdatesSelection: true,
allowedBlocks,
templateLock,
dropZoneElement: ref.current
});
const mediaElement = (0, _element.useRef)();
const currentSettings = {
isVideoBackground,
isImageBackground,
mediaElement,
hasInnerBlocks,
url,
isImgElement,
overlayColor
};
const toggleUseFeaturedImage = async () => {
const newUseFeaturedImage = !useFeaturedImage;
const averageBackgroundColor = newUseFeaturedImage ? await (0, _colorUtils.getMediaColor)(mediaUrl) : _colorUtils.DEFAULT_BACKGROUND_COLOR;
const newOverlayColor = !isUserOverlayColor ? averageBackgroundColor : overlayColor.color;
if (!isUserOverlayColor) {
if (newUseFeaturedImage) {
setOverlayColor(newOverlayColor);
} else {
setOverlayColor(undefined);
}
// Make undo revert the next setAttributes and the previous setOverlayColor.
__unstableMarkNextChangeAsNotPersistent();
}
const newDimRatio = dimRatio === 100 ? 50 : dimRatio;
const newIsDark = (0, _colorUtils.compositeIsDark)(newDimRatio, newOverlayColor, averageBackgroundColor);
setAttributes({
id: undefined,
url: undefined,
useFeaturedImage: newUseFeaturedImage,
dimRatio: newDimRatio,
backgroundType: useFeaturedImage ? _shared.IMAGE_BACKGROUND_TYPE : undefined,
isDark: newIsDark
});
};
const blockControls = /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockControls.default, {
attributes: attributes,
setAttributes: setAttributes,
onSelectMedia: onSelectMedia,
currentSettings: currentSettings,
toggleUseFeaturedImage: toggleUseFeaturedImage,
onClearMedia: onClearMedia
});
const inspectorControls = /*#__PURE__*/(0, _jsxRuntime.jsx)(_inspectorControls.default, {
attributes: attributes,
setAttributes: setAttributes,
clientId: clientId,
setOverlayColor: onSetOverlayColor,
coverRef: ref,
currentSettings: currentSettings,
toggleUseFeaturedImage: toggleUseFeaturedImage,
updateDimRatio: onUpdateDimRatio,
onClearMedia: onClearMedia,
featuredImage: media
});
const resizableCoverProps = {
className: 'block-library-cover__resize-container',
clientId,
height,
minHeight: minHeightWithUnit,
onResizeStart: () => {
setAttributes({
minHeightUnit: 'px'
});
toggleSelection(false);
},
onResize: value => {
setAttributes({
minHeight: value
});
},
onResizeStop: newMinHeight => {
toggleSelection(true);
setAttributes({
minHeight: newMinHeight
});
},
// Hide the resize handle if an aspect ratio is set, as the aspect ratio takes precedence.
showHandle: !attributes.style?.dimensions?.aspectRatio,
size: resizableBoxDimensions,
width
};
if (!useFeaturedImage && !hasInnerBlocks && !hasBackground) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [blockControls, inspectorControls, hasNonContentControls && isSelected && /*#__PURE__*/(0, _jsxRuntime.jsx)(_resizableCoverPopover.default, {
...resizableCoverProps
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(TagName, {
...blockProps,
className: (0, _clsx.default)('is-placeholder', blockProps.className),
style: {
...blockProps.style,
minHeight: minHeightWithUnit || undefined
},
children: [resizeListener, /*#__PURE__*/(0, _jsxRuntime.jsx)(_coverPlaceholder.default, {
onSelectMedia: onSelectMedia,
onError: onUploadError,
toggleUseFeaturedImage: toggleUseFeaturedImage,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: "wp-block-cover__placeholder-background-options",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.ColorPalette, {
disableCustomColors: true,
value: overlayColor.color,
onChange: onSetOverlayColor,
clearable: false,
asButtons: true,
"aria-label": (0, _i18n.__)('Overlay color')
})
})
})]
})]
});
}
const classes = (0, _clsx.default)({
'is-dark-theme': isDark,
'is-light': !isDark,
'is-transient': isUploadingMedia,
'has-parallax': hasParallax,
'is-repeated': isRepeated,
'has-custom-content-position': !(0, _shared.isContentPositionCenter)(contentPosition)
}, (0, _shared.getPositionClassName)(contentPosition));
const showOverlay = url || !useFeaturedImage || useFeaturedImage && !url;
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [blockControls, inspectorControls, /*#__PURE__*/(0, _jsxRuntime.jsxs)(TagName, {
...blockProps,
className: (0, _clsx.default)(classes, blockProps.className),
style: {
...style,
...blockProps.style
},
"data-url": url,
children: [resizeListener, !url && useFeaturedImage && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Placeholder, {
className: "wp-block-cover__image--placeholder-image",
withIllustration: true
}), url && isImageBackground && (isImgElement ? /*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
ref: mediaElement,
className: "wp-block-cover__image-background",
alt: alt,
src: url,
style: mediaStyle
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
ref: mediaElement,
role: alt ? 'img' : undefined,
"aria-label": alt ? alt : undefined,
className: (0, _clsx.default)(classes, 'wp-block-cover__image-background'),
style: {
backgroundImage,
backgroundPosition
}
})), url && isVideoBackground && /*#__PURE__*/(0, _jsxRuntime.jsx)("video", {
ref: mediaElement,
className: "wp-block-cover__video-background",
autoPlay: true,
muted: true,
loop: true,
src: url,
style: mediaStyle
}), showOverlay && /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
"aria-hidden": "true",
className: (0, _clsx.default)('wp-block-cover__background', (0, _shared.dimRatioToClass)(dimRatio), {
[overlayColor.class]: overlayColor.class,
'has-background-dim': dimRatio !== undefined,
// For backwards compatibility. Former versions of the Cover Block applied
// `.wp-block-cover__gradient-background` in the presence of
// media, a gradient and a dim.
'wp-block-cover__gradient-background': url && gradientValue && dimRatio !== 0,
'has-background-gradient': gradientValue,
[gradientClass]: gradientClass
}),
style: {
backgroundImage: gradientValue,
...bgStyle
}
}), isUploadingMedia && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_coverPlaceholder.default, {
disableMediaButtons: true,
onSelectMedia: onSelectMedia,
onError: onUploadError,
toggleUseFeaturedImage: toggleUseFeaturedImage
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
...innerBlocksProps
})]
}), hasNonContentControls && isSelected && /*#__PURE__*/(0, _jsxRuntime.jsx)(_resizableCoverPopover.default, {
...resizableCoverProps
})]
});
}
var _default = exports.default = (0, _compose.compose)([(0, _blockEditor.withColors)({
overlayColor: 'background-color'
})])(CoverEdit);
//# sourceMappingURL=index.js.map