@wordpress/block-library
Version:
Block library for the WordPress editor.
921 lines (920 loc) • 30.6 kB
JavaScript
// packages/block-library/src/image/image.js
import { isBlobURL } from "@wordpress/blob";
import {
ExternalLink,
ResizableBox,
Spinner,
TextareaControl,
TextControl,
ToolbarButton,
ToolbarGroup,
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
__experimentalUseCustomUnits as useCustomUnits,
Placeholder,
MenuItem,
ToolbarItem,
DropdownMenu,
Popover
} from "@wordpress/components";
import {
useMergeRefs,
useResizeObserver,
useViewportMatch
} from "@wordpress/compose";
import { useSelect, useDispatch } from "@wordpress/data";
import {
BlockControls,
InspectorControls,
__experimentalImageURLInputUI as ImageURLInputUI,
MediaReplaceFlow,
store as blockEditorStore,
useSettings,
__experimentalImageEditor as ImageEditor,
__experimentalUseBorderProps as useBorderProps,
__experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles,
privateApis as blockEditorPrivateApis,
BlockSettingsMenuControls
} from "@wordpress/block-editor";
import { useCallback, useEffect, useMemo, useState } from "@wordpress/element";
import { __, _x, sprintf, isRTL } from "@wordpress/i18n";
import { getFilename } from "@wordpress/url";
import { getBlockBindingsSource, switchToBlockType } from "@wordpress/blocks";
import { crop, overlayText, upload, chevronDown } from "@wordpress/icons";
import { store as noticesStore } from "@wordpress/notices";
import { store as coreStore } from "@wordpress/core-data";
import { unlock } from "../lock-unlock";
import { createUpgradedEmbedBlock } from "../embed/util";
import { isExternalImage } from "./edit";
import { Caption } from "../utils/caption";
import { useToolsPanelDropdownMenuProps } from "../utils/hooks";
import {
MIN_SIZE,
ALLOWED_MEDIA_TYPES,
SIZED_LAYOUTS,
DEFAULT_MEDIA_SIZE_SLUG
} from "./constants";
import { evalAspectRatio } from "./utils";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var { DimensionsTool, ResolutionTool } = unlock(blockEditorPrivateApis);
var scaleOptions = [
{
value: "cover",
label: _x("Cover", "Scale option for dimensions control"),
help: __("Image covers the space evenly.")
},
{
value: "contain",
label: _x("Contain", "Scale option for dimensions control"),
help: __("Image is contained without distortion.")
}
];
var WRITEMODE_POPOVER_PROPS = {
placement: "bottom-start"
};
var ImageWrapper = ({ href, children }) => {
if (!href) {
return children;
}
return /* @__PURE__ */ jsx(
"a",
{
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
}
);
};
function ContentOnlyControls({
attributes,
setAttributes,
lockAltControls,
lockAltControlsMessage,
lockTitleControls,
lockTitleControlsMessage
}) {
const [popoverAnchor, setPopoverAnchor] = useState(null);
const [isAltDialogOpen, setIsAltDialogOpen] = useState(false);
const [isTitleDialogOpen, setIsTitleDialogOpen] = useState(false);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(ToolbarItem, { ref: setPopoverAnchor, children: (toggleProps) => /* @__PURE__ */ jsx(
DropdownMenu,
{
icon: chevronDown,
label: __("More"),
toggleProps: {
...toggleProps,
description: __("Displays more controls.")
},
popoverProps: WRITEMODE_POPOVER_PROPS,
children: ({ onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
MenuItem,
{
onClick: () => {
setIsAltDialogOpen(true);
onClose();
},
"aria-haspopup": "dialog",
children: _x(
"Alternative text",
"Alternative text for an image. Block toolbar label, a low character count is preferred."
)
}
),
/* @__PURE__ */ jsx(
MenuItem,
{
onClick: () => {
setIsTitleDialogOpen(true);
onClose();
},
"aria-haspopup": "dialog",
children: __("Title text")
}
)
] })
}
) }),
isAltDialogOpen && /* @__PURE__ */ jsx(
Popover,
{
placement: "bottom-start",
anchor: popoverAnchor,
onClose: () => setIsAltDialogOpen(false),
offset: 13,
variant: "toolbar",
children: /* @__PURE__ */ jsx("div", { className: "wp-block-image__toolbar_content_textarea__container", children: /* @__PURE__ */ jsx(
TextareaControl,
{
className: "wp-block-image__toolbar_content_textarea",
label: __("Alternative text"),
value: attributes.alt || "",
onChange: (value) => setAttributes({ alt: value }),
disabled: lockAltControls,
help: lockAltControls ? /* @__PURE__ */ jsx(Fragment, { children: lockAltControlsMessage }) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
ExternalLink,
{
href: (
// translators: Localized tutorial, if one exists. W3C Web Accessibility Initiative link has list of existing translations.
__(
"https://www.w3.org/WAI/tutorials/images/decision-tree/"
)
),
children: __(
"Describe the purpose of the image."
)
}
),
/* @__PURE__ */ jsx("br", {}),
__("Leave empty if decorative.")
] }),
__nextHasNoMarginBottom: true
}
) })
}
),
isTitleDialogOpen && /* @__PURE__ */ jsx(
Popover,
{
placement: "bottom-start",
anchor: popoverAnchor,
onClose: () => setIsTitleDialogOpen(false),
offset: 13,
variant: "toolbar",
children: /* @__PURE__ */ jsx("div", { className: "wp-block-image__toolbar_content_textarea__container", children: /* @__PURE__ */ jsx(
TextControl,
{
__next40pxDefaultSize: true,
className: "wp-block-image__toolbar_content_textarea",
__nextHasNoMarginBottom: true,
label: __("Title attribute"),
value: attributes.title || "",
onChange: (value) => setAttributes({
title: value
}),
disabled: lockTitleControls,
help: lockTitleControls ? /* @__PURE__ */ jsx(Fragment, { children: lockTitleControlsMessage }) : /* @__PURE__ */ jsxs(Fragment, { children: [
__(
"Describe the role of this image on the page."
),
/* @__PURE__ */ jsx(ExternalLink, { href: "https://www.w3.org/TR/html52/dom.html#the-title-attribute", children: __(
"(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] = useState();
const [resizeDelta, setResizeDelta] = useState(null);
const [pixelSize, setPixelSize] = useState({});
const [offsetTop, setOffsetTop] = useState(0);
const setResizeObserved = useResizeObserver(([entry]) => {
if (!resizeDelta) {
const [box] = entry.borderBoxSize;
setPixelSize({ width: box.inlineSize, height: box.blockSize });
}
setOffsetTop(entry.target.offsetTop);
});
const effectResizeableBoxPlacement = useCallback(() => {
setOffsetTop(imageElement?.offsetTop ?? 0);
}, [imageElement]);
const setRefs = useMergeRefs([setImageElement, setResizeObserved]);
const { allowResize = true } = context;
const image = useSelect(
(select) => id && isSingleSelected ? select(coreStore).getEntityRecord(
"postType",
"attachment",
id,
{ context: "view" }
) : null,
[id, isSingleSelected]
);
const { canInsertCover, imageEditing, imageSizes, maxWidth } = useSelect(
(select) => {
const { getBlockRootClientId, canInsertBlockType, getSettings: getSettings2 } = select(blockEditorStore);
const rootClientId = getBlockRootClientId(clientId);
const settings = getSettings2();
return {
imageEditing: settings.imageEditing,
imageSizes: settings.imageSizes,
maxWidth: settings.maxWidth,
canInsertCover: canInsertBlockType(
"core/cover",
rootClientId
)
};
},
[clientId]
);
const { getBlock, getSettings } = useSelect(blockEditorStore);
const { replaceBlocks, toggleSelection } = useDispatch(blockEditorStore);
const { createErrorNotice, createSuccessNotice } = useDispatch(noticesStore);
const { editEntityRecord } = useDispatch(coreStore);
const isLargeViewport = useViewportMatch("medium");
const isWideAligned = ["wide", "full"].includes(align);
const [
{ loadedNaturalWidth, loadedNaturalHeight },
setLoadedNaturalSize
] = useState({});
const [isEditingImage, setIsEditingImage] = useState(false);
const [externalBlob, setExternalBlob] = useState();
const [hasImageErrored, setHasImageErrored] = 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 }));
useEffect(() => {
if (!isExternalImage(id, url) || !isSingleSelected || !getSettings().mediaUpload) {
setExternalBlob();
return;
}
if (externalBlob) {
return;
}
window.fetch(url.includes("?") ? url : url + "?").then((response) => response.blob()).then((blob) => setExternalBlob(blob)).catch(() => {
});
}, [id, url, isSingleSelected, externalBlob, getSettings]);
const { naturalWidth, naturalHeight } = useMemo(() => {
return {
naturalWidth: imageElement?.naturalWidth || loadedNaturalWidth || void 0,
naturalHeight: imageElement?.naturalHeight || loadedNaturalHeight || void 0
};
}, [loadedNaturalWidth, loadedNaturalHeight, imageElement?.complete]);
function onImageError() {
setHasImageErrored(true);
const embedBlock = createUpgradedEmbedBlock({ attributes: { url } });
if (void 0 !== 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: void 0
});
}
}
function resetLightbox() {
if (lightboxSetting?.enabled && lightboxSetting?.allowEditing) {
setAttributes({
lightbox: { enabled: false }
});
} else {
setAttributes({
lightbox: void 0
});
}
}
function onSetTitle(value) {
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([img2]) {
onSelectImage(img2);
if (isBlobURL(img2.url)) {
return;
}
setExternalBlob();
createSuccessNotice(__("Image uploaded."), {
type: "snackbar"
});
},
allowedTypes: ALLOWED_MEDIA_TYPES,
onError(message) {
createErrorNotice(message, { type: "snackbar" });
}
});
}
useEffect(() => {
if (!isSingleSelected) {
setIsEditingImage(false);
}
}, [isSingleSelected]);
const canEditImage = id && naturalWidth && naturalHeight && imageEditing;
const allowCrop = isSingleSelected && canEditImage && !isEditingImage && !isContentOnlyMode;
function switchToCover() {
replaceBlocks(
clientId,
switchToBlockType(getBlock(clientId), "core/cover")
);
}
const dimensionsUnitsOptions = useCustomUnits({
availableUnits: ["px"]
});
const [lightboxSetting] = 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 = useToolsPanelDropdownMenuProps();
const dimensionsControl = isResizable && (SIZED_LAYOUTS.includes(parentLayoutType) ? /* @__PURE__ */ jsx(
DimensionsTool,
{
value: { aspectRatio },
onChange: ({ aspectRatio: newAspectRatio }) => {
setAttributes({
aspectRatio: newAspectRatio,
scale: "cover"
});
},
defaultAspectRatio: "auto",
tools: ["aspectRatio"]
}
) : /* @__PURE__ */ jsx(
DimensionsTool,
{
value: { width, height, scale, aspectRatio },
onChange: ({
width: newWidth,
height: newHeight,
scale: newScale,
aspectRatio: newAspectRatio
}) => {
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,
unitsOptions: dimensionsUnitsOptions
}
));
const resetAll = () => {
setAttributes({
alt: void 0,
width: void 0,
height: void 0,
scale: void 0,
aspectRatio: void 0,
lightbox: void 0
});
updateImage(DEFAULT_MEDIA_SIZE_SLUG);
};
const sizeControls = /* @__PURE__ */ jsx(InspectorControls, { children: /* @__PURE__ */ jsx(
ToolsPanel,
{
label: __("Settings"),
resetAll,
dropdownMenuProps,
children: dimensionsControl
}
) });
const arePatternOverridesEnabled = metadata?.bindings?.__default?.source === "core/pattern-overrides";
const {
lockUrlControls = false,
lockHrefControls = false,
lockAltControls = false,
lockAltControlsMessage,
lockTitleControls = false,
lockTitleControlsMessage,
hideCaptionControls = false
} = useSelect(
(select) => {
if (!isSingleSelected) {
return {};
}
const {
url: urlBinding,
alt: altBinding,
title: titleBinding,
caption: captionBinding
} = metadata?.bindings || {};
const hasParentPattern = !!context["pattern/overrides"];
const urlBindingSource = getBlockBindingsSource(
urlBinding?.source
);
const altBindingSource = getBlockBindingsSource(
altBinding?.source
);
const titleBindingSource = 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
),
hideCaptionControls: !!captionBinding,
lockAltControls: !!altBinding && !altBindingSource?.canUserEditValue?.({
select,
context,
args: altBinding?.args
}),
lockAltControlsMessage: altBindingSource?.label ? sprintf(
/* translators: %s: Label of the bindings source. */
__("Connected to %s"),
altBindingSource.label
) : __("Connected to dynamic data"),
lockTitleControls: !!titleBinding && !titleBindingSource?.canUserEditValue?.({
select,
context,
args: titleBinding?.args
}),
lockTitleControlsMessage: titleBindingSource?.label ? sprintf(
/* translators: %s: Label of the bindings source. */
__("Connected to %s"),
titleBindingSource.label
) : __("Connected to dynamic data")
};
},
[
arePatternOverridesEnabled,
context,
isSingleSelected,
metadata?.bindings
]
);
const showUrlInput = isSingleSelected && !isEditingImage && !lockHrefControls && !lockUrlControls;
const showCoverControls = isSingleSelected && canInsertCover && !isContentOnlyMode;
const showBlockControls = showUrlInput || allowCrop || showCoverControls;
const mediaReplaceFlow = isSingleSelected && !isEditingImage && !lockUrlControls && // For contentOnly mode, put this button in its own area so it has borders around it.
/* @__PURE__ */ jsx(BlockControls, { group: isContentOnlyMode ? "inline" : "other", children: /* @__PURE__ */ jsx(
MediaReplaceFlow,
{
mediaId: id,
mediaURL: url,
allowedTypes: ALLOWED_MEDIA_TYPES,
onSelect: onSelectImage,
onSelectURL,
onError: onUploadError,
name: !url ? __("Add image") : __("Replace"),
onReset: () => onSelectImage(void 0)
}
) });
const controls = /* @__PURE__ */ jsxs(Fragment, { children: [
showBlockControls && /* @__PURE__ */ jsxs(BlockControls, { group: "block", children: [
showUrlInput && /* @__PURE__ */ jsx(
ImageURLInputUI,
{
url: href || "",
onChangeUrl: onSetHref,
linkDestination,
mediaUrl: image && image.source_url || url,
mediaLink: image && image.link,
linkTarget,
linkClass,
rel,
showLightboxSetting,
lightboxEnabled: lightboxChecked,
onSetLightbox,
resetLightbox
}
),
allowCrop && /* @__PURE__ */ jsx(
ToolbarButton,
{
onClick: () => setIsEditingImage(true),
icon: crop,
label: __("Crop")
}
),
showCoverControls && /* @__PURE__ */ jsx(
ToolbarButton,
{
icon: overlayText,
label: __("Add text over image"),
onClick: switchToCover
}
)
] }),
isSingleSelected && externalBlob && /* @__PURE__ */ jsx(BlockControls, { children: /* @__PURE__ */ jsx(ToolbarGroup, { children: /* @__PURE__ */ jsx(
ToolbarButton,
{
onClick: uploadExternal,
icon: upload,
label: __("Upload to Media Library")
}
) }) }),
isContentOnlyMode && // 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.
/* @__PURE__ */ jsx(BlockControls, { group: "block", children: /* @__PURE__ */ jsx(
ContentOnlyControls,
{
attributes,
setAttributes,
lockAltControls,
lockAltControlsMessage,
lockTitleControls,
lockTitleControlsMessage
}
) }),
/* @__PURE__ */ jsx(InspectorControls, { children: /* @__PURE__ */ jsxs(
ToolsPanel,
{
label: __("Settings"),
resetAll,
dropdownMenuProps,
children: [
isSingleSelected && /* @__PURE__ */ jsx(
ToolsPanelItem,
{
label: __("Alternative text"),
isShownByDefault: true,
hasValue: () => !!alt,
onDeselect: () => setAttributes({ alt: void 0 }),
children: /* @__PURE__ */ jsx(
TextareaControl,
{
label: __("Alternative text"),
value: alt || "",
onChange: updateAlt,
readOnly: lockAltControls,
help: lockAltControls ? /* @__PURE__ */ jsx(Fragment, { children: lockAltControlsMessage }) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
ExternalLink,
{
href: (
// translators: Localized tutorial, if one exists. W3C Web Accessibility Initiative link has list of existing translations.
__(
"https://www.w3.org/WAI/tutorials/images/decision-tree/"
)
),
children: __(
"Describe the purpose of the image."
)
}
),
/* @__PURE__ */ jsx("br", {}),
__(
"Leave empty if decorative."
)
] }),
__nextHasNoMarginBottom: true
}
)
}
),
dimensionsControl,
!!imageSizeOptions.length && /* @__PURE__ */ jsx(
ResolutionTool,
{
value: sizeSlug,
defaultValue: DEFAULT_MEDIA_SIZE_SLUG,
onChange: updateImage,
options: imageSizeOptions
}
)
]
}
) }),
/* @__PURE__ */ jsx(InspectorControls, { group: "advanced", children: /* @__PURE__ */ jsx(
TextControl,
{
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __("Title attribute"),
value: title || "",
onChange: onSetTitle,
readOnly: lockTitleControls,
help: lockTitleControls ? /* @__PURE__ */ jsx(Fragment, { children: lockTitleControlsMessage }) : /* @__PURE__ */ jsxs(Fragment, { children: [
__(
"Describe the role of this image on the page."
),
/* @__PURE__ */ jsx(ExternalLink, { href: "https://www.w3.org/TR/html52/dom.html#the-title-attribute", children: __(
"(Note: many devices and browsers do not display this text.)"
) })
] })
}
) })
] });
const filename = getFilename(url);
let defaultedAlt;
if (alt) {
defaultedAlt = alt;
} else if (filename) {
defaultedAlt = sprintf(
/* translators: %s: file name */
__("This image has an empty alt attribute; its file name is %s"),
filename
);
} else {
defaultedAlt = __("This image has an empty alt attribute");
}
const borderProps = useBorderProps(attributes);
const shadowProps = getShadowClassesAndStyles(attributes);
const isRounded = attributes.className?.includes("is-style-rounded");
const { postType, postId, queryId } = context;
const isDescendentOfQueryLoop = Number.isFinite(queryId);
let img = temporaryURL && hasImageErrored ? (
// 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.
/* @__PURE__ */ jsx(
Placeholder,
{
className: "wp-block-image__placeholder",
withIllustration: true,
children: /* @__PURE__ */ jsx(Spinner, {})
}
)
) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ 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__ */ jsx(Spinner, {})
] });
if (canEditImage && isEditingImage) {
img = /* @__PURE__ */ jsx(ImageWrapper, { href, children: /* @__PURE__ */ jsx(
ImageEditor,
{
id,
url,
...pixelSize,
naturalHeight,
naturalWidth,
onSaveImage: (imageAttributes) => setAttributes(imageAttributes),
onFinishEditing: () => {
setIsEditingImage(false);
},
borderProps: isRounded ? void 0 : borderProps
}
) });
} else {
img = /* @__PURE__ */ jsx(ImageWrapper, { href, children: img });
}
let resizableBox;
if (isResizable && isSingleSelected && !isEditingImage && !SIZED_LAYOUTS.includes(parentLayoutType)) {
const numericRatio = aspectRatio && evalAspectRatio(aspectRatio);
const customRatio = pixelSize.width / pixelSize.height;
const naturalRatio = naturalWidth / naturalHeight;
const ratio = numericRatio || customRatio || naturalRatio || 1;
const minWidth = naturalWidth < naturalHeight ? MIN_SIZE : MIN_SIZE * ratio;
const minHeight = naturalHeight < naturalWidth ? MIN_SIZE : MIN_SIZE / ratio;
const maxWidthBuffer = maxWidth * 2.5;
const maxResizeWidth = maxContentWidth || maxWidthBuffer;
let showRightHandle = false;
let showLeftHandle = false;
if (align === "center") {
showRightHandle = true;
showLeftHandle = true;
} else if (isRTL()) {
if (align === "left") {
showRightHandle = true;
} else {
showLeftHandle = true;
}
} else {
if (align === "right") {
showLeftHandle = true;
} else {
showRightHandle = true;
}
}
resizableBox = /* @__PURE__ */ jsx(
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,
maxWidth: maxResizeWidth,
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
}));
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: void 0,
height: void 0
});
return;
}
setAttributes({
width: `${elt.offsetWidth}px`,
height: "auto",
aspectRatio: ratio === naturalRatio ? void 0 : String(ratio)
});
},
resizeRatio: align === "center" ? 2 : 1
}
);
}
if (!url && !temporaryURL) {
return /* @__PURE__ */ jsxs(Fragment, { children: [
mediaReplaceFlow,
metadata?.bindings ? controls : sizeControls
] });
}
const setPostFeatureImage = () => {
editEntityRecord("postType", postType, postId, {
featured_media: id
});
createSuccessNotice(__("Post featured image updated."), {
type: "snackbar"
});
};
const featuredImageControl = /* @__PURE__ */ jsx(BlockSettingsMenuControls, { children: ({ selectedClientIds }) => selectedClientIds.length === 1 && !isDescendentOfQueryLoop && postId && id && clientId === selectedClientIds[0] && /* @__PURE__ */ jsx(MenuItem, { onClick: setPostFeatureImage, children: __("Set as featured image") }) });
return /* @__PURE__ */ jsxs(Fragment, { children: [
mediaReplaceFlow,
controls,
featuredImageControl,
img,
resizableBox,
/* @__PURE__ */ jsx(
Caption,
{
attributes,
setAttributes,
isSelected: isSingleSelected,
insertBlocksAfter,
label: __("Image caption text"),
showToolbarButton: isSingleSelected && (hasNonContentControls || isContentOnlyMode) && !hideCaptionControls
}
)
] });
}
export {
Image as default
};
//# sourceMappingURL=image.js.map