@wordpress/block-library
Version:
Block library for the WordPress editor.
455 lines (453 loc) • 17.7 kB
JavaScript
;
// packages/block-library/src/image/view.js
var import_interactivity = require("@wordpress/interactivity");
var import_constants = require("./constants.cjs");
var isTouching = false;
var lastTouchTime = 0;
var touchStartEvent = {
startX: 0,
startY: 0,
startTime: 0
};
var focusableSelectors = [
".wp-lightbox-close-button",
".wp-lightbox-navigation-button"
];
function getImageSrc({ uploadedSrc }) {
return uploadedSrc || "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
}
function getImageSrcset({ lightboxSrcset }) {
return lightboxSrcset || "";
}
var { state, actions, callbacks } = (0, import_interactivity.store)(
"core/image",
{
state: {
selectedImageId: null,
selectedGalleryId: null,
preloadTimers: /* @__PURE__ */ new Map(),
preloadedImageIds: /* @__PURE__ */ new Set(),
get galleryImages() {
if (!state.selectedGalleryId) {
return [state.selectedImageId];
}
return Object.entries(state.metadata).filter(
([, value]) => value.galleryId === state.selectedGalleryId
).sort(([, a], [, b]) => {
const orderA = a.order ?? 0;
const orderB = b.order ?? 0;
return orderA - orderB;
}).map(([key]) => key);
},
get selectedImageIndex() {
return state.galleryImages.findIndex(
(id) => id === state.selectedImageId
);
},
get selectedImage() {
return state.metadata[state.selectedImageId];
},
get hasNavigationIcon() {
const { navigationButtonType } = state.selectedImage;
return navigationButtonType === "icon" || navigationButtonType === "both";
},
get hasNavigationText() {
const { navigationButtonType } = state.selectedImage;
return navigationButtonType === "text" || navigationButtonType === "both";
},
get thisImage() {
const { imageId } = (0, import_interactivity.getContext)();
return state.metadata[imageId];
},
get hasNavigation() {
return state.galleryImages.length > 1;
},
get hasNextImage() {
return state.selectedImageIndex + 1 < state.galleryImages.length;
},
get hasPreviousImage() {
return state.selectedImageIndex - 1 >= 0;
},
get overlayOpened() {
return state.selectedImageId !== null;
},
get roleAttribute() {
return state.overlayOpened ? "dialog" : null;
},
get ariaModal() {
return state.overlayOpened ? "true" : null;
},
get ariaLabel() {
return state.selectedImage.customAriaLabel || (0, import_interactivity.getConfig)().defaultAriaLabel;
},
get closeButtonAriaLabel() {
return state.hasNavigationText ? void 0 : (0, import_interactivity.getConfig)().closeButtonText;
},
get prevButtonAriaLabel() {
return state.hasNavigationText ? void 0 : (0, import_interactivity.getConfig)().prevButtonText;
},
get nextButtonAriaLabel() {
return state.hasNavigationText ? void 0 : (0, import_interactivity.getConfig)().nextButtonText;
},
get enlargedSrc() {
return getImageSrc(state.selectedImage);
},
get enlargedSrcset() {
return getImageSrcset(state.selectedImage);
},
get figureStyles() {
return state.overlayOpened && `${state.selectedImage.figureStyles?.replace(
/margin[^;]*;?/g,
""
)};`;
},
get imgStyles() {
return state.overlayOpened && `${state.selectedImage.imgStyles?.replace(
/;$/,
""
)}; object-fit:cover;`;
},
get isContentHidden() {
const ctx = (0, import_interactivity.getContext)();
return state.overlayEnabled && state.selectedImageId === ctx.imageId;
},
get isContentVisible() {
const ctx = (0, import_interactivity.getContext)();
return !state.overlayEnabled && state.selectedImageId === ctx.imageId;
}
},
actions: {
showLightbox() {
const { imageId } = (0, import_interactivity.getContext)();
if (!state.metadata[imageId].imageRef?.complete) {
return;
}
state.scrollTopReset = document.documentElement.scrollTop;
state.scrollLeftReset = document.documentElement.scrollLeft;
state.selectedImageId = imageId;
const { galleryId } = (0, import_interactivity.getContext)("core/gallery") || {};
state.selectedGalleryId = galleryId || null;
state.overlayEnabled = true;
callbacks.setOverlayStyles();
},
hideLightbox() {
if (state.overlayEnabled) {
state.overlayEnabled = false;
setTimeout(function() {
state.selectedImage.buttonRef.focus({
preventScroll: true
});
state.selectedImageId = null;
state.selectedGalleryId = null;
}, 450);
}
},
showPreviousImage: (0, import_interactivity.withSyncEvent)((event) => {
event.stopPropagation();
const nextIndex = state.hasPreviousImage ? state.selectedImageIndex - 1 : state.galleryImages.length - 1;
state.selectedImageId = state.galleryImages[nextIndex];
callbacks.setOverlayStyles();
}),
showNextImage: (0, import_interactivity.withSyncEvent)((event) => {
event.stopPropagation();
const nextIndex = state.hasNextImage ? state.selectedImageIndex + 1 : 0;
state.selectedImageId = state.galleryImages[nextIndex];
callbacks.setOverlayStyles();
}),
handleKeydown: (0, import_interactivity.withSyncEvent)((event) => {
if (state.overlayEnabled) {
if (event.key === "Escape") {
actions.hideLightbox();
} else if (event.key === "ArrowLeft") {
actions.showPreviousImage(event);
} else if (event.key === "ArrowRight") {
actions.showNextImage(event);
} else if (event.key === "Tab") {
const focusableElements = Array.from(
document.querySelectorAll(focusableSelectors)
);
const firstFocusableElement = focusableElements[0];
const lastFocusableElement = focusableElements[focusableElements.length - 1];
if (event.shiftKey && event.target === firstFocusableElement) {
event.preventDefault();
lastFocusableElement.focus();
} else if (!event.shiftKey && event.target === lastFocusableElement) {
event.preventDefault();
firstFocusableElement.focus();
}
}
}
}),
handleTouchMove: (0, import_interactivity.withSyncEvent)((event) => {
if (state.overlayEnabled) {
event.preventDefault();
}
}),
handleTouchStart(event) {
isTouching = true;
const t = event.touches && event.touches[0];
if (t) {
touchStartEvent.startX = t.clientX;
touchStartEvent.startY = t.clientY;
touchStartEvent.startTime = Date.now();
}
},
handleTouchEnd: (0, import_interactivity.withSyncEvent)((event) => {
const touchEndEvent = event.changedTouches && event.changedTouches[0] || event.touches && event.touches[0];
const now = Date.now();
if (touchEndEvent && state.overlayEnabled) {
const deltaX = touchEndEvent.clientX - touchStartEvent.startX;
const deltaY = touchEndEvent.clientY - touchStartEvent.startY;
const absDeltaX = Math.abs(deltaX);
const absDeltaY = Math.abs(deltaY);
const elapsedMs = now - touchStartEvent.startTime;
const isHorizontalSwipe = (
// Swipe distance is greater than 50px
absDeltaX > 50 && // Horizontal movement is much larger than the vertical movement
absDeltaX > absDeltaY * 1.5 && // Fast action of less than 800ms
elapsedMs < 800
);
if (isHorizontalSwipe) {
event.preventDefault();
if (deltaX < 0) {
actions.showNextImage(event);
} else {
actions.showPreviousImage(event);
}
}
}
lastTouchTime = now;
isTouching = false;
}),
handleScroll() {
if (state.overlayOpened) {
if (!isTouching && Date.now() - lastTouchTime > 450) {
window.scrollTo(
state.scrollLeftReset,
state.scrollTopReset
);
}
}
},
preloadImage() {
const { imageId } = (0, import_interactivity.getContext)();
if (state.preloadedImageIds.has(imageId)) {
return;
}
const imageMetadata = state.metadata[imageId];
const imageLink = document.createElement("link");
imageLink.rel = "preload";
imageLink.as = "image";
imageLink.href = getImageSrc(imageMetadata);
const srcset = getImageSrcset(imageMetadata);
if (srcset) {
imageLink.setAttribute("imagesrcset", srcset);
imageLink.setAttribute("imagesizes", "100vw");
}
document.head.appendChild(imageLink);
state.preloadedImageIds.add(imageId);
},
preloadImageWithDelay() {
const { imageId } = (0, import_interactivity.getContext)();
actions.cancelPreload();
const timerId = setTimeout(
(0, import_interactivity.withScope)(() => {
actions.preloadImage();
state.preloadTimers.delete(imageId);
}),
import_constants.IMAGE_PRELOAD_DELAY
);
state.preloadTimers.set(imageId, timerId);
},
cancelPreload() {
const { imageId } = (0, import_interactivity.getContext)();
if (state.preloadTimers.has(imageId)) {
clearTimeout(state.preloadTimers.get(imageId));
state.preloadTimers.delete(imageId);
}
}
},
callbacks: {
setOverlayStyles() {
if (!state.overlayEnabled) {
return;
}
let {
naturalWidth,
naturalHeight,
offsetWidth: originalWidth,
offsetHeight: originalHeight
} = state.selectedImage.imageRef;
let { x: screenPosX, y: screenPosY } = state.selectedImage.imageRef.getBoundingClientRect();
const naturalRatio = naturalWidth / naturalHeight;
let originalRatio = originalWidth / originalHeight;
if (state.selectedImage.scaleAttr === "contain") {
if (naturalRatio > originalRatio) {
const heightWithoutSpace = originalWidth / naturalRatio;
screenPosY += (originalHeight - heightWithoutSpace) / 2;
originalHeight = heightWithoutSpace;
} else {
const widthWithoutSpace = originalHeight * naturalRatio;
screenPosX += (originalWidth - widthWithoutSpace) / 2;
originalWidth = widthWithoutSpace;
}
}
originalRatio = originalWidth / originalHeight;
let imgMaxWidth = parseFloat(
state.selectedImage.targetWidth && state.selectedImage.targetWidth !== "none" ? state.selectedImage.targetWidth : naturalWidth
);
let imgMaxHeight = parseFloat(
state.selectedImage.targetHeight && state.selectedImage.targetHeight !== "none" ? state.selectedImage.targetHeight : naturalHeight
);
let imgRatio = imgMaxWidth / imgMaxHeight;
let containerMaxWidth = imgMaxWidth;
let containerMaxHeight = imgMaxHeight;
let containerWidth = imgMaxWidth;
let containerHeight = imgMaxHeight;
if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) {
if (naturalRatio > imgRatio) {
const reducedHeight = imgMaxWidth / naturalRatio;
if (imgMaxHeight - reducedHeight > imgMaxWidth) {
imgMaxHeight = reducedHeight;
imgMaxWidth = reducedHeight * naturalRatio;
} else {
imgMaxHeight = imgMaxWidth / naturalRatio;
}
} else {
const reducedWidth = imgMaxHeight * naturalRatio;
if (imgMaxWidth - reducedWidth > imgMaxHeight) {
imgMaxWidth = reducedWidth;
imgMaxHeight = reducedWidth / naturalRatio;
} else {
imgMaxWidth = imgMaxHeight * naturalRatio;
}
}
containerWidth = imgMaxWidth;
containerHeight = imgMaxHeight;
imgRatio = imgMaxWidth / imgMaxHeight;
if (originalRatio > imgRatio) {
containerMaxWidth = imgMaxWidth;
containerMaxHeight = containerMaxWidth / originalRatio;
} else {
containerMaxHeight = imgMaxHeight;
containerMaxWidth = containerMaxHeight * originalRatio;
}
}
if (originalWidth > containerWidth || originalHeight > containerHeight) {
containerWidth = originalWidth;
containerHeight = originalHeight;
}
let horizontalPadding = 0;
let verticalPadding = 160;
if (480 < window.innerWidth) {
horizontalPadding = 80;
verticalPadding = 160;
}
if (960 < window.innerWidth) {
horizontalPadding = state.hasNavigation ? 320 : 80;
verticalPadding = 80;
}
const targetMaxWidth = Math.min(
window.innerWidth - horizontalPadding,
containerWidth
);
const targetMaxHeight = Math.min(
window.innerHeight - verticalPadding,
containerHeight
);
const targetContainerRatio = targetMaxWidth / targetMaxHeight;
if (originalRatio > targetContainerRatio) {
containerWidth = targetMaxWidth;
containerHeight = containerWidth / originalRatio;
} else {
containerHeight = targetMaxHeight;
containerWidth = containerHeight * originalRatio;
}
const containerScale = originalWidth / containerWidth;
const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth);
const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight);
state.overlayStyles = `
--wp--lightbox-initial-top-position: ${screenPosY}px;
--wp--lightbox-initial-left-position: ${screenPosX}px;
--wp--lightbox-container-width: ${containerWidth + 1}px;
--wp--lightbox-container-height: ${containerHeight + 1}px;
--wp--lightbox-image-width: ${lightboxImgWidth}px;
--wp--lightbox-image-height: ${lightboxImgHeight}px;
--wp--lightbox-scale: ${containerScale};
--wp--lightbox-scrollbar-width: ${window.innerWidth - document.documentElement.clientWidth}px;
`;
},
setButtonStyles() {
const { ref } = (0, import_interactivity.getElement)();
if (!ref) {
return;
}
const { imageId } = (0, import_interactivity.getContext)();
state.metadata[imageId].imageRef = ref;
state.metadata[imageId].currentSrc = ref.currentSrc;
const {
naturalWidth,
naturalHeight,
offsetWidth,
offsetHeight
} = ref;
if (naturalWidth === 0 || naturalHeight === 0) {
return;
}
const figure = ref.parentElement;
const figureWidth = ref.parentElement.clientWidth;
let figureHeight = ref.parentElement.clientHeight;
const caption = figure.querySelector("figcaption");
if (caption) {
const captionComputedStyle = window.getComputedStyle(caption);
if (!["absolute", "fixed"].includes(
captionComputedStyle.position
)) {
figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom);
}
}
const buttonOffsetTop = figureHeight - offsetHeight;
const buttonOffsetRight = figureWidth - offsetWidth;
let buttonTop = buttonOffsetTop + 16;
let buttonRight = buttonOffsetRight + 16;
if (state.metadata[imageId].scaleAttr === "contain") {
const naturalRatio = naturalWidth / naturalHeight;
const offsetRatio = offsetWidth / offsetHeight;
if (naturalRatio >= offsetRatio) {
const referenceHeight = offsetWidth / naturalRatio;
buttonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16;
buttonRight = buttonOffsetRight + 16;
} else {
const referenceWidth = offsetHeight * naturalRatio;
buttonTop = buttonOffsetTop + 16;
buttonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16;
}
}
state.metadata[imageId].buttonTop = buttonTop;
state.metadata[imageId].buttonRight = buttonRight;
},
setOverlayFocus() {
if (state.overlayEnabled) {
const { ref } = (0, import_interactivity.getElement)();
ref.focus();
}
},
setInertElements() {
document.querySelectorAll("body > :not(.wp-lightbox-overlay)").forEach((el) => {
if (state.overlayEnabled) {
el.setAttribute("inert", "");
} else {
el.removeAttribute("inert");
}
});
},
initTriggerButton() {
const { imageId } = (0, import_interactivity.getContext)();
const { ref } = (0, import_interactivity.getElement)();
state.metadata[imageId].buttonRef = ref;
}
}
},
{ lock: true }
);
//# sourceMappingURL=view.cjs.map