@wordpress/block-library
Version:
Block library for the WordPress editor.
929 lines (916 loc) • 35.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Image;
var _blob = require("@wordpress/blob");
var _components = require("@wordpress/components");
var _compose = require("@wordpress/compose");
var _data = require("@wordpress/data");
var _blockEditor = require("@wordpress/block-editor");
var _element = require("@wordpress/element");
var _i18n = require("@wordpress/i18n");
var _url = require("@wordpress/url");
var _blocks = require("@wordpress/blocks");
var _icons = require("@wordpress/icons");
var _notices = require("@wordpress/notices");
var _coreData = require("@wordpress/core-data");
var _lockUnlock = require("../lock-unlock");
var _util = require("../embed/util");
var _edit = require("./edit");
var _caption = require("../utils/caption");
var _hooks = require("../utils/hooks");
var _constants = require("./constants");
var _utils = require("./utils");
var _jsxRuntime = require("react/jsx-runtime");
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const {
DimensionsTool,
ResolutionTool
} = (0, _lockUnlock.unlock)(_blockEditor.privateApis);
const scaleOptions = [{
value: 'cover',
label: (0, _i18n._x)('Cover', 'Scale option for dimensions control'),
help: (0, _i18n.__)('Image covers the space evenly.')
}, {
value: 'contain',
label: (0, _i18n._x)('Contain', 'Scale option for dimensions control'),
help: (0, _i18n.__)('Image is contained without distortion.')
}];
const WRITEMODE_POPOVER_PROPS = {
placement: 'bottom-start'
};
// If the image has a href, wrap in an <a /> tag to trigger any inherited link element styles.
const ImageWrapper = ({
href,
children
}) => {
if (!href) {
return children;
}
return /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
href: href,
onClick: event => event.preventDefault(),
"aria-disabled": true,
style: {
// When the Image block is linked,
// it's wrapped with a disabled <a /> tag.
// Restore cursor style so it doesn't appear 'clickable'
// and remove pointer events. Safari needs the display property.
pointerEvents: 'none',
cursor: 'default',
display: 'inline'
},
children: children
});
};
function ContentOnlyControls({
attributes,
setAttributes,
lockAltControls,
lockAltControlsMessage,
lockTitleControls,
lockTitleControlsMessage
}) {
// Use internal state instead of a ref to make sure that the component
// re-renders when the popover's anchor updates.
const [popoverAnchor, setPopoverAnchor] = (0, _element.useState)(null);
const [isAltDialogOpen, setIsAltDialogOpen] = (0, _element.useState)(false);
const [isTitleDialogOpen, setIsTitleDialogOpen] = (0, _element.useState)(false);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarItem, {
ref: setPopoverAnchor,
children: toggleProps => /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.DropdownMenu, {
icon: _icons.chevronDown
/* translators: button label text should, if possible, be under 16 characters. */,
label: (0, _i18n.__)('More'),
toggleProps: {
...toggleProps,
description: (0, _i18n.__)('Displays more controls.')
},
popoverProps: WRITEMODE_POPOVER_PROPS,
children: ({
onClose
}) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MenuItem, {
onClick: () => {
setIsAltDialogOpen(true);
onClose();
},
"aria-haspopup": "dialog",
children: (0, _i18n._x)('Alternative text', 'Alternative text for an image. Block toolbar label, a low character count is preferred.')
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MenuItem, {
onClick: () => {
setIsTitleDialogOpen(true);
onClose();
},
"aria-haspopup": "dialog",
children: (0, _i18n.__)('Title text')
})]
})
})
}), isAltDialogOpen && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Popover, {
placement: "bottom-start",
anchor: popoverAnchor,
onClose: () => setIsAltDialogOpen(false),
offset: 13,
variant: "toolbar",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: "wp-block-image__toolbar_content_textarea__container",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.TextareaControl, {
className: "wp-block-image__toolbar_content_textarea",
label: (0, _i18n.__)('Alternative text'),
value: attributes.alt || '',
onChange: value => setAttributes({
alt: value
}),
disabled: lockAltControls,
help: lockAltControls ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: lockAltControlsMessage
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ExternalLink, {
href:
// translators: Localized tutorial, if one exists. W3C Web Accessibility Initiative link has list of existing translations.
(0, _i18n.__)('https://www.w3.org/WAI/tutorials/images/decision-tree/'),
children: (0, _i18n.__)('Describe the purpose of the image.')
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("br", {}), (0, _i18n.__)('Leave empty if decorative.')]
}),
__nextHasNoMarginBottom: true
})
})
}), isTitleDialogOpen && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Popover, {
placement: "bottom-start",
anchor: popoverAnchor,
onClose: () => setIsTitleDialogOpen(false),
offset: 13,
variant: "toolbar",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: "wp-block-image__toolbar_content_textarea__container",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.TextControl, {
__next40pxDefaultSize: true,
className: "wp-block-image__toolbar_content_textarea",
__nextHasNoMarginBottom: true,
label: (0, _i18n.__)('Title attribute'),
value: attributes.title || '',
onChange: value => setAttributes({
title: value
}),
disabled: lockTitleControls,
help: lockTitleControls ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: lockTitleControlsMessage
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [(0, _i18n.__)('Describe the role of this image on the page.'), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ExternalLink, {
href: "https://www.w3.org/TR/html52/dom.html#the-title-attribute",
children: (0, _i18n.__)('(Note: many devices and browsers do not display this text.)')
})]
})
})
})
})]
});
}
function Image({
temporaryURL,
attributes,
setAttributes,
isSingleSelected,
insertBlocksAfter,
onReplace,
onSelectImage,
onSelectURL,
onUploadError,
context,
clientId,
blockEditingMode,
parentLayoutType,
maxContentWidth
}) {
const {
url = '',
alt,
align,
id,
href,
rel,
linkClass,
linkDestination,
title,
width,
height,
aspectRatio,
scale,
linkTarget,
sizeSlug,
lightbox,
metadata
} = attributes;
const [imageElement, setImageElement] = (0, _element.useState)();
const [resizeDelta, setResizeDelta] = (0, _element.useState)(null);
const [pixelSize, setPixelSize] = (0, _element.useState)({});
const [offsetTop, setOffsetTop] = (0, _element.useState)(0);
const setResizeObserved = (0, _compose.useResizeObserver)(([entry]) => {
if (!resizeDelta) {
const [box] = entry.borderBoxSize;
setPixelSize({
width: box.inlineSize,
height: box.blockSize
});
}
// This is usually 0 unless the image height is less than the line-height.
setOffsetTop(entry.target.offsetTop);
});
const effectResizeableBoxPlacement = (0, _element.useCallback)(() => {
var _imageElement$offsetT;
setOffsetTop((_imageElement$offsetT = imageElement?.offsetTop) !== null && _imageElement$offsetT !== void 0 ? _imageElement$offsetT : 0);
}, [imageElement]);
const setRefs = (0, _compose.useMergeRefs)([setImageElement, setResizeObserved]);
const {
allowResize = true
} = context;
const image = (0, _data.useSelect)(select => id && isSingleSelected ? select(_coreData.store).getMedia(id, {
context: 'view'
}) : null, [id, isSingleSelected]);
const {
canInsertCover,
imageEditing,
imageSizes,
maxWidth
} = (0, _data.useSelect)(select => {
const {
getBlockRootClientId,
canInsertBlockType,
getSettings
} = select(_blockEditor.store);
const rootClientId = getBlockRootClientId(clientId);
const settings = getSettings();
return {
imageEditing: settings.imageEditing,
imageSizes: settings.imageSizes,
maxWidth: settings.maxWidth,
canInsertCover: canInsertBlockType('core/cover', rootClientId)
};
}, [clientId]);
const {
getBlock,
getSettings
} = (0, _data.useSelect)(_blockEditor.store);
const {
replaceBlocks,
toggleSelection
} = (0, _data.useDispatch)(_blockEditor.store);
const {
createErrorNotice,
createSuccessNotice
} = (0, _data.useDispatch)(_notices.store);
const {
editEntityRecord
} = (0, _data.useDispatch)(_coreData.store);
const isLargeViewport = (0, _compose.useViewportMatch)('medium');
const isWideAligned = ['wide', 'full'].includes(align);
const [{
loadedNaturalWidth,
loadedNaturalHeight
}, setLoadedNaturalSize] = (0, _element.useState)({});
const [isEditingImage, setIsEditingImage] = (0, _element.useState)(false);
const [externalBlob, setExternalBlob] = (0, _element.useState)();
const [hasImageErrored, setHasImageErrored] = (0, _element.useState)(false);
const hasNonContentControls = blockEditingMode === 'default';
const isContentOnlyMode = blockEditingMode === 'contentOnly';
const isResizable = allowResize && hasNonContentControls && !isWideAligned && isLargeViewport;
const imageSizeOptions = imageSizes.filter(({
slug
}) => image?.media_details?.sizes?.[slug]?.source_url).map(({
name,
slug
}) => ({
value: slug,
label: name
}));
// If an image is externally hosted, try to fetch the image data. This may
// fail if the image host doesn't allow CORS with the domain. If it works,
// we can enable a button in the toolbar to upload the image.
(0, _element.useEffect)(() => {
if (!(0, _edit.isExternalImage)(id, url) || !isSingleSelected || !getSettings().mediaUpload) {
setExternalBlob();
return;
}
if (externalBlob) {
return;
}
window
// Avoid cache, which seems to help avoid CORS problems.
.fetch(url.includes('?') ? url : url + '?').then(response => response.blob()).then(blob => setExternalBlob(blob))
// Do nothing, cannot upload.
.catch(() => {});
}, [id, url, isSingleSelected, externalBlob, getSettings]);
// Get naturalWidth and naturalHeight from image, and fall back to loaded natural
// width and height. This resolves an issue in Safari where the loaded natural
// width and height is otherwise lost when switching between alignments.
// See: https://github.com/WordPress/gutenberg/pull/37210.
const {
naturalWidth,
naturalHeight
} = (0, _element.useMemo)(() => {
return {
naturalWidth: imageElement?.naturalWidth || loadedNaturalWidth || undefined,
naturalHeight: imageElement?.naturalHeight || loadedNaturalHeight || undefined
};
}, [loadedNaturalWidth, loadedNaturalHeight, imageElement?.complete]);
function onImageError() {
setHasImageErrored(true);
// Check if there's an embed block that handles this URL, e.g., instagram URL.
// See: https://github.com/WordPress/gutenberg/pull/11472
const embedBlock = (0, _util.createUpgradedEmbedBlock)({
attributes: {
url
}
});
if (undefined !== embedBlock) {
onReplace(embedBlock);
}
}
function onImageLoad(event) {
setHasImageErrored(false);
setLoadedNaturalSize({
loadedNaturalWidth: event.target?.naturalWidth,
loadedNaturalHeight: event.target?.naturalHeight
});
}
function onSetHref(props) {
setAttributes(props);
}
function onSetLightbox(enable) {
if (enable && !lightboxSetting?.enabled) {
setAttributes({
lightbox: {
enabled: true
}
});
} else if (!enable && lightboxSetting?.enabled) {
setAttributes({
lightbox: {
enabled: false
}
});
} else {
setAttributes({
lightbox: undefined
});
}
}
function resetLightbox() {
// When deleting a link from an image while lightbox settings
// are enabled by default, we should disable the lightbox,
// otherwise the resulting UX looks like a mistake.
// See https://github.com/WordPress/gutenberg/pull/59890/files#r1532286123.
if (lightboxSetting?.enabled && lightboxSetting?.allowEditing) {
setAttributes({
lightbox: {
enabled: false
}
});
} else {
setAttributes({
lightbox: undefined
});
}
}
function onSetTitle(value) {
// This is the HTML title attribute, separate from the media object
// title.
setAttributes({
title: value
});
}
function updateAlt(newAlt) {
setAttributes({
alt: newAlt
});
}
function updateImage(newSizeSlug) {
const newUrl = image?.media_details?.sizes?.[newSizeSlug]?.source_url;
if (!newUrl) {
return null;
}
setAttributes({
url: newUrl,
sizeSlug: newSizeSlug
});
}
function uploadExternal() {
const {
mediaUpload
} = getSettings();
if (!mediaUpload) {
return;
}
mediaUpload({
filesList: [externalBlob],
onFileChange([img]) {
onSelectImage(img);
if ((0, _blob.isBlobURL)(img.url)) {
return;
}
setExternalBlob();
createSuccessNotice((0, _i18n.__)('Image uploaded.'), {
type: 'snackbar'
});
},
allowedTypes: _constants.ALLOWED_MEDIA_TYPES,
onError(message) {
createErrorNotice(message, {
type: 'snackbar'
});
}
});
}
(0, _element.useEffect)(() => {
if (!isSingleSelected) {
setIsEditingImage(false);
}
}, [isSingleSelected]);
const canEditImage = id && naturalWidth && naturalHeight && imageEditing;
const allowCrop = isSingleSelected && canEditImage && !isEditingImage && !isContentOnlyMode;
function switchToCover() {
replaceBlocks(clientId, (0, _blocks.switchToBlockType)(getBlock(clientId), 'core/cover'));
}
// TODO: Can allow more units after figuring out how they should interact
// with the ResizableBox and ImageEditor components. Calculations later on
// for those components are currently assuming px units.
const dimensionsUnitsOptions = (0, _components.__experimentalUseCustomUnits)({
availableUnits: ['px']
});
const [lightboxSetting] = (0, _blockEditor.useSettings)('lightbox');
const showLightboxSetting =
// If a block-level override is set, we should give users the option to
// remove that override, even if the lightbox UI is disabled in the settings.
!!lightbox && lightbox?.enabled !== lightboxSetting?.enabled || lightboxSetting?.allowEditing;
const lightboxChecked = !!lightbox?.enabled || !lightbox && !!lightboxSetting?.enabled;
const dropdownMenuProps = (0, _hooks.useToolsPanelDropdownMenuProps)();
const dimensionsControl = isResizable && (_constants.SIZED_LAYOUTS.includes(parentLayoutType) ? /*#__PURE__*/(0, _jsxRuntime.jsx)(DimensionsTool, {
value: {
aspectRatio
},
onChange: ({
aspectRatio: newAspectRatio
}) => {
setAttributes({
aspectRatio: newAspectRatio,
scale: 'cover'
});
},
defaultAspectRatio: "auto",
tools: ['aspectRatio']
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(DimensionsTool, {
value: {
width,
height,
scale,
aspectRatio
},
onChange: ({
width: newWidth,
height: newHeight,
scale: newScale,
aspectRatio: newAspectRatio
}) => {
// Rebuilding the object forces setting `undefined`
// for values that are removed since setAttributes
// doesn't do anything with keys that aren't set.
setAttributes({
// CSS includes `height: auto`, but we need
// `width: auto` to fix the aspect ratio when
// only height is set due to the width and
// height attributes set via the server.
width: !newWidth && newHeight ? 'auto' : newWidth,
height: newHeight,
scale: newScale,
aspectRatio: newAspectRatio
});
},
defaultScale: "cover",
defaultAspectRatio: "auto",
scaleOptions: scaleOptions,
unitsOptions: dimensionsUnitsOptions
}));
const resetAll = () => {
setAttributes({
alt: undefined,
width: undefined,
height: undefined,
scale: undefined,
aspectRatio: undefined,
lightbox: undefined
});
updateImage(_constants.DEFAULT_MEDIA_SIZE_SLUG);
};
const sizeControls = /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.InspectorControls, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalToolsPanel, {
label: (0, _i18n.__)('Settings'),
resetAll: resetAll,
dropdownMenuProps: dropdownMenuProps,
children: dimensionsControl
})
});
const arePatternOverridesEnabled = metadata?.bindings?.__default?.source === 'core/pattern-overrides';
const {
lockUrlControls = false,
lockHrefControls = false,
lockAltControls = false,
lockAltControlsMessage,
lockTitleControls = false,
lockTitleControlsMessage,
lockCaption = false
} = (0, _data.useSelect)(select => {
if (!isSingleSelected) {
return {};
}
const {
url: urlBinding,
alt: altBinding,
title: titleBinding
} = metadata?.bindings || {};
const hasParentPattern = !!context['pattern/overrides'];
const urlBindingSource = (0, _blocks.getBlockBindingsSource)(urlBinding?.source);
const altBindingSource = (0, _blocks.getBlockBindingsSource)(altBinding?.source);
const titleBindingSource = (0, _blocks.getBlockBindingsSource)(titleBinding?.source);
return {
lockUrlControls: !!urlBinding && !urlBindingSource?.canUserEditValue?.({
select,
context,
args: urlBinding?.args
}),
lockHrefControls:
// Disable editing the link of the URL if the image is inside a pattern instance.
// This is a temporary solution until we support overriding the link on the frontend.
hasParentPattern || arePatternOverridesEnabled,
lockCaption:
// Disable editing the caption if the image is inside a pattern instance.
// This is a temporary solution until we support overriding the caption on the frontend.
hasParentPattern,
lockAltControls: !!altBinding && !altBindingSource?.canUserEditValue?.({
select,
context,
args: altBinding?.args
}),
lockAltControlsMessage: altBindingSource?.label ? (0, _i18n.sprintf)(/* translators: %s: Label of the bindings source. */
(0, _i18n.__)('Connected to %s'), altBindingSource.label) : (0, _i18n.__)('Connected to dynamic data'),
lockTitleControls: !!titleBinding && !titleBindingSource?.canUserEditValue?.({
select,
context,
args: titleBinding?.args
}),
lockTitleControlsMessage: titleBindingSource?.label ? (0, _i18n.sprintf)(/* translators: %s: Label of the bindings source. */
(0, _i18n.__)('Connected to %s'), titleBindingSource.label) : (0, _i18n.__)('Connected to dynamic data')
};
}, [arePatternOverridesEnabled, context, isSingleSelected, metadata?.bindings]);
const showUrlInput = isSingleSelected && !isEditingImage && !lockHrefControls && !lockUrlControls;
const showCoverControls = isSingleSelected && canInsertCover;
const showBlockControls = showUrlInput || allowCrop || showCoverControls;
const mediaReplaceFlow = isSingleSelected && !isEditingImage && !lockUrlControls &&
/*#__PURE__*/
// For contentOnly mode, put this button in its own area so it has borders around it.
(0, _jsxRuntime.jsx)(_blockEditor.BlockControls, {
group: isContentOnlyMode ? 'inline' : 'other',
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.MediaReplaceFlow, {
mediaId: id,
mediaURL: url,
allowedTypes: _constants.ALLOWED_MEDIA_TYPES,
accept: "image/*",
onSelect: onSelectImage,
onSelectURL: onSelectURL,
onError: onUploadError,
name: !url ? (0, _i18n.__)('Add image') : (0, _i18n.__)('Replace'),
onReset: () => onSelectImage(undefined)
})
});
const controls = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [showBlockControls && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_blockEditor.BlockControls, {
group: "block",
children: [showUrlInput && /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.__experimentalImageURLInputUI, {
url: href || '',
onChangeUrl: onSetHref,
linkDestination: linkDestination,
mediaUrl: image && image.source_url || url,
mediaLink: image && image.link,
linkTarget: linkTarget,
linkClass: linkClass,
rel: rel,
showLightboxSetting: showLightboxSetting,
lightboxEnabled: lightboxChecked,
onSetLightbox: onSetLightbox,
resetLightbox: resetLightbox
}), allowCrop && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarButton, {
onClick: () => setIsEditingImage(true),
icon: _icons.crop,
label: (0, _i18n.__)('Crop')
}), showCoverControls && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarButton, {
icon: _icons.overlayText,
label: (0, _i18n.__)('Add text over image'),
onClick: switchToCover
})]
}), isSingleSelected && externalBlob && /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.BlockControls, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarGroup, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarButton, {
onClick: uploadExternal,
icon: _icons.upload,
label: (0, _i18n.__)('Upload to Media Library')
})
})
}), isContentOnlyMode &&
/*#__PURE__*/
// Add some extra controls for content attributes when content only mode is active.
// With content only mode active, the inspector is hidden, so users need another way
// to edit these attributes.
(0, _jsxRuntime.jsx)(_blockEditor.BlockControls, {
group: "block",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ContentOnlyControls, {
attributes: attributes,
setAttributes: setAttributes,
lockAltControls: lockAltControls,
lockAltControlsMessage: lockAltControlsMessage,
lockTitleControls: lockTitleControls,
lockTitleControlsMessage: lockTitleControlsMessage
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.InspectorControls, {
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.__experimentalToolsPanel, {
label: (0, _i18n.__)('Settings'),
resetAll: resetAll,
dropdownMenuProps: dropdownMenuProps,
children: [isSingleSelected && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.__experimentalToolsPanelItem, {
label: (0, _i18n.__)('Alternative text'),
isShownByDefault: true,
hasValue: () => !!alt,
onDeselect: () => setAttributes({
alt: undefined
}),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.TextareaControl, {
label: (0, _i18n.__)('Alternative text'),
value: alt || '',
onChange: updateAlt,
readOnly: lockAltControls,
help: lockAltControls ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: lockAltControlsMessage
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ExternalLink, {
href:
// translators: Localized tutorial, if one exists. W3C Web Accessibility Initiative link has list of existing translations.
(0, _i18n.__)('https://www.w3.org/WAI/tutorials/images/decision-tree/'),
children: (0, _i18n.__)('Describe the purpose of the image.')
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("br", {}), (0, _i18n.__)('Leave empty if decorative.')]
}),
__nextHasNoMarginBottom: true
})
}), dimensionsControl, !!imageSizeOptions.length && /*#__PURE__*/(0, _jsxRuntime.jsx)(ResolutionTool, {
value: sizeSlug,
defaultValue: _constants.DEFAULT_MEDIA_SIZE_SLUG,
onChange: updateImage,
options: imageSizeOptions
})]
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.InspectorControls, {
group: "advanced",
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.TextControl, {
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: (0, _i18n.__)('Title attribute'),
value: title || '',
onChange: onSetTitle,
readOnly: lockTitleControls,
help: lockTitleControls ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: lockTitleControlsMessage
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [(0, _i18n.__)('Describe the role of this image on the page.'), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ExternalLink, {
href: "https://www.w3.org/TR/html52/dom.html#the-title-attribute",
children: (0, _i18n.__)('(Note: many devices and browsers do not display this text.)')
})]
})
})
})]
});
const filename = (0, _url.getFilename)(url);
let defaultedAlt;
if (alt) {
defaultedAlt = alt;
} else if (filename) {
defaultedAlt = (0, _i18n.sprintf)(/* translators: %s: file name */
(0, _i18n.__)('This image has an empty alt attribute; its file name is %s'), filename);
} else {
defaultedAlt = (0, _i18n.__)('This image has an empty alt attribute');
}
const borderProps = (0, _blockEditor.__experimentalUseBorderProps)(attributes);
const shadowProps = (0, _blockEditor.__experimentalGetShadowClassesAndStyles)(attributes);
const isRounded = attributes.className?.includes('is-style-rounded');
const {
postType,
postId,
queryId
} = context;
const isDescendentOfQueryLoop = Number.isFinite(queryId);
let img = temporaryURL && hasImageErrored ?
/*#__PURE__*/
// Show a placeholder during upload when the blob URL can't be loaded. This can
// happen when the user uploads a HEIC image in a browser that doesn't support them.
(0, _jsxRuntime.jsx)(_components.Placeholder, {
className: "wp-block-image__placeholder",
withIllustration: true,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {})
}) :
/*#__PURE__*/
// Disable reason: Image itself is not meant to be interactive, but
// should direct focus to block.
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
src: temporaryURL || url,
alt: defaultedAlt,
onError: onImageError,
onLoad: onImageLoad,
ref: setRefs,
className: borderProps.className,
width: naturalWidth,
height: naturalHeight,
style: {
aspectRatio,
...(resizeDelta ? {
width: pixelSize.width + resizeDelta.width,
height: pixelSize.height + resizeDelta.height
} : {
width,
height
}),
objectFit: scale,
...borderProps.style,
...shadowProps.style
}
}), temporaryURL && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {})]
})
/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */;
if (canEditImage && isEditingImage) {
img = /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageWrapper, {
href: href,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.__experimentalImageEditor, {
id: id,
url: url,
...pixelSize,
naturalHeight: naturalHeight,
naturalWidth: naturalWidth,
onSaveImage: imageAttributes => setAttributes(imageAttributes),
onFinishEditing: () => {
setIsEditingImage(false);
},
borderProps: isRounded ? undefined : borderProps
})
});
} else {
img = /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageWrapper, {
href: href,
children: img
});
}
let resizableBox;
if (isResizable && isSingleSelected && !isEditingImage && !_constants.SIZED_LAYOUTS.includes(parentLayoutType)) {
const numericRatio = aspectRatio && (0, _utils.evalAspectRatio)(aspectRatio);
const customRatio = pixelSize.width / pixelSize.height;
const naturalRatio = naturalWidth / naturalHeight;
const ratio = numericRatio || customRatio || naturalRatio || 1;
const minWidth = naturalWidth < naturalHeight ? _constants.MIN_SIZE : _constants.MIN_SIZE * ratio;
const minHeight = naturalHeight < naturalWidth ? _constants.MIN_SIZE : _constants.MIN_SIZE / ratio;
// With the current implementation of ResizableBox, an image needs an
// explicit pixel value for the max-width. In absence of being able to
// set the content-width, this max-width is currently dictated by the
// vanilla editor style. The following variable adds a buffer to this
// vanilla style, so 3rd party themes have some wiggleroom. This does,
// in most cases, allow you to scale the image beyond the width of the
// main column, though not infinitely.
// @todo It would be good to revisit this once a content-width variable
// becomes available.
const maxWidthBuffer = maxWidth * 2.5;
const maxResizeWidth = maxContentWidth || maxWidthBuffer;
let showRightHandle = false;
let showLeftHandle = false;
/* eslint-disable no-lonely-if */
// See https://github.com/WordPress/gutenberg/issues/7584.
if (align === 'center') {
// When the image is centered, show both handles.
showRightHandle = true;
showLeftHandle = true;
} else if ((0, _i18n.isRTL)()) {
// In RTL mode the image is on the right by default.
// Show the right handle and hide the left handle only when it is
// aligned left. Otherwise always show the left handle.
if (align === 'left') {
showRightHandle = true;
} else {
showLeftHandle = true;
}
} else {
// Show the left handle and hide the right handle only when the
// image is aligned right. Otherwise always show the right handle.
if (align === 'right') {
showLeftHandle = true;
} else {
showRightHandle = true;
}
}
/* eslint-enable no-lonely-if */
resizableBox = /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ResizableBox, {
ref: effectResizeableBoxPlacement,
style: {
position: 'absolute',
// To match the vertical-align: bottom of the img (from style.scss)
// syncs the top with the img. This matters when the img height is
// less than the line-height.
inset: `${offsetTop}px 0 0 0`
},
size: pixelSize,
minWidth: minWidth,
maxWidth: maxResizeWidth,
minHeight: minHeight,
maxHeight: maxResizeWidth / ratio,
lockAspectRatio: ratio,
enable: {
top: false,
right: showRightHandle,
bottom: true,
left: showLeftHandle
},
onResizeStart: () => {
toggleSelection(false);
},
onResize: (event, direction, elt, delta) => {
setResizeDelta(delta);
},
onResizeStop: (event, direction, elt, delta) => {
toggleSelection(true);
setResizeDelta(null);
setPixelSize(current => ({
width: current.width + delta.width,
height: current.height + delta.height
}));
// Clear hardcoded width if the resized width is close to the max-content width.
if (maxContentWidth &&
// Only do this if the image is bigger than the container to prevent it from being squished.
// TODO: Remove this check if the image support setting 100% width.
naturalWidth >= maxContentWidth && Math.abs(elt.offsetWidth - maxContentWidth) < 10) {
setAttributes({
width: undefined,
height: undefined
});
return;
}
// Since the aspect ratio is locked when resizing, we can
// use the width of the resized element to calculate the
// height in CSS to prevent stretching when the max-width
// is reached.
setAttributes({
width: `${elt.offsetWidth}px`,
height: 'auto',
aspectRatio: ratio === naturalRatio ? undefined : String(ratio)
});
},
resizeRatio: align === 'center' ? 2 : 1
});
}
if (!url && !temporaryURL) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [mediaReplaceFlow, metadata?.bindings ? controls : sizeControls]
});
}
/**
* Set the post's featured image with the current image.
*/
const setPostFeatureImage = () => {
editEntityRecord('postType', postType, postId, {
featured_media: id
});
createSuccessNotice((0, _i18n.__)('Post featured image updated.'), {
type: 'snackbar'
});
};
const featuredImageControl = /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.BlockSettingsMenuControls, {
children: ({
selectedClientIds
}) => selectedClientIds.length === 1 && !isDescendentOfQueryLoop && postId && id && clientId === selectedClientIds[0] && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MenuItem, {
onClick: setPostFeatureImage,
children: (0, _i18n.__)('Set as featured image')
})
});
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [mediaReplaceFlow, controls, featuredImageControl, img, resizableBox, /*#__PURE__*/(0, _jsxRuntime.jsx)(_caption.Caption, {
attributes: attributes,
setAttributes: setAttributes,
isSelected: isSingleSelected,
insertBlocksAfter: insertBlocksAfter,
label: (0, _i18n.__)('Image caption text'),
showToolbarButton: isSingleSelected && hasNonContentControls && !arePatternOverridesEnabled,
readOnly: lockCaption
})]
});
}
//# sourceMappingURL=image.js.map
;