[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/dist/script-modules/block-library/image/ -> view.js (source)

   1  // packages/block-library/build-module/image/view.mjs
   2  import {
   3    store,
   4    getContext,
   5    getElement,
   6    getConfig,
   7    withSyncEvent,
   8    withScope
   9  } from "@wordpress/interactivity";
  10  
  11  // packages/block-library/build-module/image/constants.mjs
  12  var IMAGE_PRELOAD_DELAY = 200;
  13  
  14  // packages/block-library/build-module/image/view.mjs
  15  var isTouching = false;
  16  var lastTouchTime = 0;
  17  var touchStartEvent = {
  18    startX: 0,
  19    startY: 0,
  20    startTime: 0
  21  };
  22  var focusableSelectors = [
  23    ".wp-lightbox-close-button",
  24    ".wp-lightbox-navigation-button"
  25  ];
  26  function getImageSrc({ uploadedSrc }) {
  27    return uploadedSrc || "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
  28  }
  29  function getImageSrcset({ lightboxSrcset }) {
  30    return lightboxSrcset || "";
  31  }
  32  var { state, actions, callbacks } = store(
  33    "core/image",
  34    {
  35      state: {
  36        selectedImageId: null,
  37        selectedGalleryId: null,
  38        preloadTimers: /* @__PURE__ */ new Map(),
  39        preloadedImageIds: /* @__PURE__ */ new Set(),
  40        get galleryImages() {
  41          if (!state.selectedGalleryId) {
  42            return [state.selectedImageId];
  43          }
  44          return Object.entries(state.metadata).filter(
  45            ([, value]) => value.galleryId === state.selectedGalleryId
  46          ).sort(([, a], [, b]) => {
  47            const orderA = a.order ?? 0;
  48            const orderB = b.order ?? 0;
  49            return orderA - orderB;
  50          }).map(([key]) => key);
  51        },
  52        get selectedImageIndex() {
  53          return state.galleryImages.findIndex(
  54            (id) => id === state.selectedImageId
  55          );
  56        },
  57        get selectedImage() {
  58          return state.metadata[state.selectedImageId];
  59        },
  60        get hasNavigationIcon() {
  61          const { navigationButtonType } = state.selectedImage;
  62          return navigationButtonType === "icon" || navigationButtonType === "both";
  63        },
  64        get hasNavigationText() {
  65          const { navigationButtonType } = state.selectedImage;
  66          return navigationButtonType === "text" || navigationButtonType === "both";
  67        },
  68        get thisImage() {
  69          const { imageId } = getContext();
  70          return state.metadata[imageId];
  71        },
  72        get hasNavigation() {
  73          return state.galleryImages.length > 1;
  74        },
  75        get hasNextImage() {
  76          return state.selectedImageIndex + 1 < state.galleryImages.length;
  77        },
  78        get hasPreviousImage() {
  79          return state.selectedImageIndex - 1 >= 0;
  80        },
  81        get overlayOpened() {
  82          return state.selectedImageId !== null;
  83        },
  84        get roleAttribute() {
  85          return state.overlayOpened ? "dialog" : null;
  86        },
  87        get ariaModal() {
  88          return state.overlayOpened ? "true" : null;
  89        },
  90        get ariaLabel() {
  91          return state.selectedImage.customAriaLabel || getConfig().defaultAriaLabel;
  92        },
  93        get closeButtonAriaLabel() {
  94          return state.hasNavigationText ? void 0 : getConfig().closeButtonText;
  95        },
  96        get prevButtonAriaLabel() {
  97          return state.hasNavigationText ? void 0 : getConfig().prevButtonText;
  98        },
  99        get nextButtonAriaLabel() {
 100          return state.hasNavigationText ? void 0 : getConfig().nextButtonText;
 101        },
 102        get enlargedSrc() {
 103          return getImageSrc(state.selectedImage);
 104        },
 105        get enlargedSrcset() {
 106          return getImageSrcset(state.selectedImage);
 107        },
 108        get figureStyles() {
 109          return state.overlayOpened && `$state.selectedImage.figureStyles?.replace(
 110            /margin[^;]*;?/g,
 111            ""
 112          )};`;
 113        },
 114        get imgStyles() {
 115          return state.overlayOpened && `$state.selectedImage.imgStyles?.replace(
 116            /;$/,
 117            ""
 118          )}; object-fit:cover;`;
 119        },
 120        get isContentHidden() {
 121          const ctx = getContext();
 122          return state.overlayEnabled && state.selectedImageId === ctx.imageId;
 123        },
 124        get isContentVisible() {
 125          const ctx = getContext();
 126          return !state.overlayEnabled && state.selectedImageId === ctx.imageId;
 127        }
 128      },
 129      actions: {
 130        showLightbox() {
 131          const { imageId } = getContext();
 132          if (!state.metadata[imageId].imageRef?.complete) {
 133            return;
 134          }
 135          state.scrollTopReset = document.documentElement.scrollTop;
 136          state.scrollLeftReset = document.documentElement.scrollLeft;
 137          state.selectedImageId = imageId;
 138          const { galleryId } = getContext("core/gallery") || {};
 139          state.selectedGalleryId = galleryId || null;
 140          state.overlayEnabled = true;
 141          callbacks.setOverlayStyles();
 142        },
 143        hideLightbox() {
 144          if (state.overlayEnabled) {
 145            state.overlayEnabled = false;
 146            setTimeout(function() {
 147              state.selectedImage.buttonRef.focus({
 148                preventScroll: true
 149              });
 150              state.selectedImageId = null;
 151              state.selectedGalleryId = null;
 152            }, 450);
 153          }
 154        },
 155        showPreviousImage: withSyncEvent((event) => {
 156          event.stopPropagation();
 157          const nextIndex = state.hasPreviousImage ? state.selectedImageIndex - 1 : state.galleryImages.length - 1;
 158          state.selectedImageId = state.galleryImages[nextIndex];
 159          callbacks.setOverlayStyles();
 160        }),
 161        showNextImage: withSyncEvent((event) => {
 162          event.stopPropagation();
 163          const nextIndex = state.hasNextImage ? state.selectedImageIndex + 1 : 0;
 164          state.selectedImageId = state.galleryImages[nextIndex];
 165          callbacks.setOverlayStyles();
 166        }),
 167        handleKeydown: withSyncEvent((event) => {
 168          if (state.overlayEnabled) {
 169            if (event.key === "Escape") {
 170              actions.hideLightbox();
 171            } else if (event.key === "ArrowLeft") {
 172              actions.showPreviousImage(event);
 173            } else if (event.key === "ArrowRight") {
 174              actions.showNextImage(event);
 175            } else if (event.key === "Tab") {
 176              const focusableElements = Array.from(
 177                document.querySelectorAll(focusableSelectors)
 178              );
 179              const firstFocusableElement = focusableElements[0];
 180              const lastFocusableElement = focusableElements[focusableElements.length - 1];
 181              if (event.shiftKey && event.target === firstFocusableElement) {
 182                event.preventDefault();
 183                lastFocusableElement.focus();
 184              } else if (!event.shiftKey && event.target === lastFocusableElement) {
 185                event.preventDefault();
 186                firstFocusableElement.focus();
 187              }
 188            }
 189          }
 190        }),
 191        handleTouchMove: withSyncEvent((event) => {
 192          if (state.overlayEnabled) {
 193            event.preventDefault();
 194          }
 195        }),
 196        handleTouchStart(event) {
 197          isTouching = true;
 198          const t = event.touches && event.touches[0];
 199          if (t) {
 200            touchStartEvent.startX = t.clientX;
 201            touchStartEvent.startY = t.clientY;
 202            touchStartEvent.startTime = Date.now();
 203          }
 204        },
 205        handleTouchEnd: withSyncEvent((event) => {
 206          const touchEndEvent = event.changedTouches && event.changedTouches[0] || event.touches && event.touches[0];
 207          const now = Date.now();
 208          if (touchEndEvent && state.overlayEnabled) {
 209            const deltaX = touchEndEvent.clientX - touchStartEvent.startX;
 210            const deltaY = touchEndEvent.clientY - touchStartEvent.startY;
 211            const absDeltaX = Math.abs(deltaX);
 212            const absDeltaY = Math.abs(deltaY);
 213            const elapsedMs = now - touchStartEvent.startTime;
 214            const isHorizontalSwipe = (
 215              // Swipe distance is greater than 50px
 216              absDeltaX > 50 && // Horizontal movement is much larger than the vertical movement
 217              absDeltaX > absDeltaY * 1.5 && // Fast action of less than 800ms
 218              elapsedMs < 800
 219            );
 220            if (isHorizontalSwipe) {
 221              event.preventDefault();
 222              if (deltaX < 0) {
 223                actions.showNextImage(event);
 224              } else {
 225                actions.showPreviousImage(event);
 226              }
 227            }
 228          }
 229          lastTouchTime = now;
 230          isTouching = false;
 231        }),
 232        handleScroll() {
 233          if (state.overlayOpened) {
 234            if (!isTouching && Date.now() - lastTouchTime > 450) {
 235              window.scrollTo(
 236                state.scrollLeftReset,
 237                state.scrollTopReset
 238              );
 239            }
 240          }
 241        },
 242        preloadImage() {
 243          const { imageId } = getContext();
 244          if (state.preloadedImageIds.has(imageId)) {
 245            return;
 246          }
 247          const imageMetadata = state.metadata[imageId];
 248          const imageLink = document.createElement("link");
 249          imageLink.rel = "preload";
 250          imageLink.as = "image";
 251          imageLink.href = getImageSrc(imageMetadata);
 252          const srcset = getImageSrcset(imageMetadata);
 253          if (srcset) {
 254            imageLink.setAttribute("imagesrcset", srcset);
 255            imageLink.setAttribute("imagesizes", "100vw");
 256          }
 257          document.head.appendChild(imageLink);
 258          state.preloadedImageIds.add(imageId);
 259        },
 260        preloadImageWithDelay() {
 261          const { imageId } = getContext();
 262          actions.cancelPreload();
 263          const timerId = setTimeout(
 264            withScope(() => {
 265              actions.preloadImage();
 266              state.preloadTimers.delete(imageId);
 267            }),
 268            IMAGE_PRELOAD_DELAY
 269          );
 270          state.preloadTimers.set(imageId, timerId);
 271        },
 272        cancelPreload() {
 273          const { imageId } = getContext();
 274          if (state.preloadTimers.has(imageId)) {
 275            clearTimeout(state.preloadTimers.get(imageId));
 276            state.preloadTimers.delete(imageId);
 277          }
 278        }
 279      },
 280      callbacks: {
 281        setOverlayStyles() {
 282          if (!state.overlayEnabled) {
 283            return;
 284          }
 285          let {
 286            naturalWidth,
 287            naturalHeight,
 288            offsetWidth: originalWidth,
 289            offsetHeight: originalHeight
 290          } = state.selectedImage.imageRef;
 291          let { x: screenPosX, y: screenPosY } = state.selectedImage.imageRef.getBoundingClientRect();
 292          const naturalRatio = naturalWidth / naturalHeight;
 293          let originalRatio = originalWidth / originalHeight;
 294          if (state.selectedImage.scaleAttr === "contain") {
 295            if (naturalRatio > originalRatio) {
 296              const heightWithoutSpace = originalWidth / naturalRatio;
 297              screenPosY += (originalHeight - heightWithoutSpace) / 2;
 298              originalHeight = heightWithoutSpace;
 299            } else {
 300              const widthWithoutSpace = originalHeight * naturalRatio;
 301              screenPosX += (originalWidth - widthWithoutSpace) / 2;
 302              originalWidth = widthWithoutSpace;
 303            }
 304          }
 305          originalRatio = originalWidth / originalHeight;
 306          let imgMaxWidth = parseFloat(
 307            state.selectedImage.targetWidth && state.selectedImage.targetWidth !== "none" ? state.selectedImage.targetWidth : naturalWidth
 308          );
 309          let imgMaxHeight = parseFloat(
 310            state.selectedImage.targetHeight && state.selectedImage.targetHeight !== "none" ? state.selectedImage.targetHeight : naturalHeight
 311          );
 312          let imgRatio = imgMaxWidth / imgMaxHeight;
 313          let containerMaxWidth = imgMaxWidth;
 314          let containerMaxHeight = imgMaxHeight;
 315          let containerWidth = imgMaxWidth;
 316          let containerHeight = imgMaxHeight;
 317          if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) {
 318            if (naturalRatio > imgRatio) {
 319              const reducedHeight = imgMaxWidth / naturalRatio;
 320              if (imgMaxHeight - reducedHeight > imgMaxWidth) {
 321                imgMaxHeight = reducedHeight;
 322                imgMaxWidth = reducedHeight * naturalRatio;
 323              } else {
 324                imgMaxHeight = imgMaxWidth / naturalRatio;
 325              }
 326            } else {
 327              const reducedWidth = imgMaxHeight * naturalRatio;
 328              if (imgMaxWidth - reducedWidth > imgMaxHeight) {
 329                imgMaxWidth = reducedWidth;
 330                imgMaxHeight = reducedWidth / naturalRatio;
 331              } else {
 332                imgMaxWidth = imgMaxHeight * naturalRatio;
 333              }
 334            }
 335            containerWidth = imgMaxWidth;
 336            containerHeight = imgMaxHeight;
 337            imgRatio = imgMaxWidth / imgMaxHeight;
 338            if (originalRatio > imgRatio) {
 339              containerMaxWidth = imgMaxWidth;
 340              containerMaxHeight = containerMaxWidth / originalRatio;
 341            } else {
 342              containerMaxHeight = imgMaxHeight;
 343              containerMaxWidth = containerMaxHeight * originalRatio;
 344            }
 345          }
 346          if (originalWidth > containerWidth || originalHeight > containerHeight) {
 347            containerWidth = originalWidth;
 348            containerHeight = originalHeight;
 349          }
 350          let horizontalPadding = 0;
 351          let verticalPadding = 160;
 352          if (480 < window.innerWidth) {
 353            horizontalPadding = 80;
 354            verticalPadding = 160;
 355          }
 356          if (960 < window.innerWidth) {
 357            horizontalPadding = state.hasNavigation ? 320 : 80;
 358            verticalPadding = 80;
 359          }
 360          const targetMaxWidth = Math.min(
 361            window.innerWidth - horizontalPadding,
 362            containerWidth
 363          );
 364          const targetMaxHeight = Math.min(
 365            window.innerHeight - verticalPadding,
 366            containerHeight
 367          );
 368          const targetContainerRatio = targetMaxWidth / targetMaxHeight;
 369          if (originalRatio > targetContainerRatio) {
 370            containerWidth = targetMaxWidth;
 371            containerHeight = containerWidth / originalRatio;
 372          } else {
 373            containerHeight = targetMaxHeight;
 374            containerWidth = containerHeight * originalRatio;
 375          }
 376          const containerScale = originalWidth / containerWidth;
 377          const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth);
 378          const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight);
 379          state.overlayStyles = `
 380                      --wp--lightbox-initial-top-position: $screenPosY}px;
 381                      --wp--lightbox-initial-left-position: $screenPosX}px;
 382                      --wp--lightbox-container-width: $containerWidth + 1}px;
 383                      --wp--lightbox-container-height: $containerHeight + 1}px;
 384                      --wp--lightbox-image-width: $lightboxImgWidth}px;
 385                      --wp--lightbox-image-height: $lightboxImgHeight}px;
 386                      --wp--lightbox-scale: $containerScale};
 387                      --wp--lightbox-scrollbar-width: $window.innerWidth - document.documentElement.clientWidth}px;
 388                  `;
 389        },
 390        setButtonStyles() {
 391          const { ref } = getElement();
 392          if (!ref) {
 393            return;
 394          }
 395          const { imageId } = getContext();
 396          state.metadata[imageId].imageRef = ref;
 397          state.metadata[imageId].currentSrc = ref.currentSrc;
 398          const {
 399            naturalWidth,
 400            naturalHeight,
 401            offsetWidth,
 402            offsetHeight
 403          } = ref;
 404          if (naturalWidth === 0 || naturalHeight === 0) {
 405            return;
 406          }
 407          const figure = ref.parentElement;
 408          const figureWidth = ref.parentElement.clientWidth;
 409          let figureHeight = ref.parentElement.clientHeight;
 410          const caption = figure.querySelector("figcaption");
 411          if (caption) {
 412            const captionComputedStyle = window.getComputedStyle(caption);
 413            if (!["absolute", "fixed"].includes(
 414              captionComputedStyle.position
 415            )) {
 416              figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom);
 417            }
 418          }
 419          const buttonOffsetTop = figureHeight - offsetHeight;
 420          const buttonOffsetRight = figureWidth - offsetWidth;
 421          let buttonTop = buttonOffsetTop + 16;
 422          let buttonRight = buttonOffsetRight + 16;
 423          if (state.metadata[imageId].scaleAttr === "contain") {
 424            const naturalRatio = naturalWidth / naturalHeight;
 425            const offsetRatio = offsetWidth / offsetHeight;
 426            if (naturalRatio >= offsetRatio) {
 427              const referenceHeight = offsetWidth / naturalRatio;
 428              buttonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16;
 429              buttonRight = buttonOffsetRight + 16;
 430            } else {
 431              const referenceWidth = offsetHeight * naturalRatio;
 432              buttonTop = buttonOffsetTop + 16;
 433              buttonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16;
 434            }
 435          }
 436          state.metadata[imageId].buttonTop = buttonTop;
 437          state.metadata[imageId].buttonRight = buttonRight;
 438        },
 439        setOverlayFocus() {
 440          if (state.overlayEnabled) {
 441            const { ref } = getElement();
 442            ref.focus();
 443          }
 444        },
 445        setInertElements() {
 446          document.querySelectorAll("body > :not(.wp-lightbox-overlay)").forEach((el) => {
 447            if (state.overlayEnabled) {
 448              el.setAttribute("inert", "");
 449            } else {
 450              el.removeAttribute("inert");
 451            }
 452          });
 453        },
 454        initTriggerButton() {
 455          const { imageId } = getContext();
 456          const { ref } = getElement();
 457          state.metadata[imageId].buttonRef = ref;
 458        }
 459      }
 460    },
 461    { lock: true }
 462  );


Generated : Sat Jun 13 09:38:55 2026 Cross-referenced by PHPXref