[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/blocks/image/ -> view.js (source)

   1  import * as __WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__ from "@wordpress/interactivity";
   2  /******/ // The require scope
   3  /******/ var __webpack_require__ = {};
   4  /******/ 
   5  /************************************************************************/
   6  /******/ /* webpack/runtime/define property getters */
   7  /******/ (() => {
   8  /******/     // define getter functions for harmony exports
   9  /******/     __webpack_require__.d = (exports, definition) => {
  10  /******/         for(var key in definition) {
  11  /******/             if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
  12  /******/                 Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
  13  /******/             }
  14  /******/         }
  15  /******/     };
  16  /******/ })();
  17  /******/ 
  18  /******/ /* webpack/runtime/hasOwnProperty shorthand */
  19  /******/ (() => {
  20  /******/     __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  21  /******/ })();
  22  /******/ 
  23  /************************************************************************/
  24  var __webpack_exports__ = {};
  25  
  26  ;// external "@wordpress/interactivity"
  27  var x = (y) => {
  28      var x = {}; __webpack_require__.d(x, y); return x
  29  } 
  30  var y = (x) => (() => (x))
  31  const interactivity_namespaceObject = x({ ["getContext"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getContext), ["getElement"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.getElement), ["store"]: () => (__WEBPACK_EXTERNAL_MODULE__wordpress_interactivity_8e89b257__.store) });
  32  ;// ./node_modules/@wordpress/block-library/build-module/image/view.js
  33  /**
  34   * WordPress dependencies
  35   */
  36  
  37  
  38  /**
  39   * Tracks whether user is touching screen; used to differentiate behavior for
  40   * touch and mouse input.
  41   *
  42   * @type {boolean}
  43   */
  44  let isTouching = false;
  45  
  46  /**
  47   * Tracks the last time the screen was touched; used to differentiate behavior
  48   * for touch and mouse input.
  49   *
  50   * @type {number}
  51   */
  52  let lastTouchTime = 0;
  53  const {
  54    state,
  55    actions,
  56    callbacks
  57  } = (0,interactivity_namespaceObject.store)('core/image', {
  58    state: {
  59      currentImageId: null,
  60      get currentImage() {
  61        return state.metadata[state.currentImageId];
  62      },
  63      get overlayOpened() {
  64        return state.currentImageId !== null;
  65      },
  66      get roleAttribute() {
  67        return state.overlayOpened ? 'dialog' : null;
  68      },
  69      get ariaModal() {
  70        return state.overlayOpened ? 'true' : null;
  71      },
  72      get enlargedSrc() {
  73        return state.currentImage.uploadedSrc || 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
  74      },
  75      get figureStyles() {
  76        return state.overlayOpened && `$state.currentImage.figureStyles?.replace(/margin[^;]*;?/g, '')};`;
  77      },
  78      get imgStyles() {
  79        return state.overlayOpened && `$state.currentImage.imgStyles?.replace(/;$/, '')}; object-fit:cover;`;
  80      },
  81      get imageButtonRight() {
  82        const {
  83          imageId
  84        } = (0,interactivity_namespaceObject.getContext)();
  85        return state.metadata[imageId].imageButtonRight;
  86      },
  87      get imageButtonTop() {
  88        const {
  89          imageId
  90        } = (0,interactivity_namespaceObject.getContext)();
  91        return state.metadata[imageId].imageButtonTop;
  92      },
  93      get isContentHidden() {
  94        const ctx = (0,interactivity_namespaceObject.getContext)();
  95        return state.overlayEnabled && state.currentImageId === ctx.imageId;
  96      },
  97      get isContentVisible() {
  98        const ctx = (0,interactivity_namespaceObject.getContext)();
  99        return !state.overlayEnabled && state.currentImageId === ctx.imageId;
 100      }
 101    },
 102    actions: {
 103      showLightbox() {
 104        const {
 105          imageId
 106        } = (0,interactivity_namespaceObject.getContext)();
 107  
 108        // Bails out if the image has not loaded yet.
 109        if (!state.metadata[imageId].imageRef?.complete) {
 110          return;
 111        }
 112  
 113        // Stores the positions of the scroll to fix it until the overlay is
 114        // closed.
 115        state.scrollTopReset = document.documentElement.scrollTop;
 116        state.scrollLeftReset = document.documentElement.scrollLeft;
 117  
 118        // Sets the current expanded image in the state and enables the overlay.
 119        state.overlayEnabled = true;
 120        state.currentImageId = imageId;
 121  
 122        // Computes the styles of the overlay for the animation.
 123        callbacks.setOverlayStyles();
 124      },
 125      hideLightbox() {
 126        if (state.overlayEnabled) {
 127          // Starts the overlay closing animation. The showClosingAnimation
 128          // class is used to avoid showing it on page load.
 129          state.showClosingAnimation = true;
 130          state.overlayEnabled = false;
 131  
 132          // Waits until the close animation has completed before allowing a
 133          // user to scroll again. The duration of this animation is defined in
 134          // the `styles.scss` file, but in any case we should wait a few
 135          // milliseconds longer than the duration, otherwise a user may scroll
 136          // too soon and cause the animation to look sloppy.
 137          setTimeout(function () {
 138            // Delays before changing the focus. Otherwise the focus ring will
 139            // appear on Firefox before the image has finished animating, which
 140            // looks broken.
 141            state.currentImage.buttonRef.focus({
 142              preventScroll: true
 143            });
 144  
 145            // Resets the current image id to mark the overlay as closed.
 146            state.currentImageId = null;
 147          }, 450);
 148        }
 149      },
 150      handleKeydown(event) {
 151        if (state.overlayEnabled) {
 152          // Focuses the close button when the user presses the tab key.
 153          if (event.key === 'Tab') {
 154            event.preventDefault();
 155            const {
 156              ref
 157            } = (0,interactivity_namespaceObject.getElement)();
 158            ref.querySelector('button').focus();
 159          }
 160          // Closes the lightbox when the user presses the escape key.
 161          if (event.key === 'Escape') {
 162            actions.hideLightbox();
 163          }
 164        }
 165      },
 166      handleTouchMove(event) {
 167        // On mobile devices, prevents triggering the scroll event because
 168        // otherwise the page jumps around when it resets the scroll position.
 169        // This also means that closing the lightbox requires that a user
 170        // perform a simple tap. This may be changed in the future if there is a
 171        // better alternative to override or reset the scroll position during
 172        // swipe actions.
 173        if (state.overlayEnabled) {
 174          event.preventDefault();
 175        }
 176      },
 177      handleTouchStart() {
 178        isTouching = true;
 179      },
 180      handleTouchEnd() {
 181        // Waits a few milliseconds before resetting to ensure that pinch to
 182        // zoom works consistently on mobile devices when the lightbox is open.
 183        lastTouchTime = Date.now();
 184        isTouching = false;
 185      },
 186      handleScroll() {
 187        // Prevents scrolling behaviors that trigger content shift while the
 188        // lightbox is open. It would be better to accomplish through CSS alone,
 189        // but using overflow: hidden is currently the only way to do so and
 190        // that causes a layout to shift and prevents the zoom animation from
 191        // working in some cases because it's not possible to account for the
 192        // layout shift when doing the animation calculations. Instead, it uses
 193        // JavaScript to prevent and reset the scrolling behavior.
 194        if (state.overlayOpened) {
 195          // Avoids overriding the scroll behavior on mobile devices because
 196          // doing so breaks the pinch to zoom functionality, and users should
 197          // be able to zoom in further on the high-res image.
 198          if (!isTouching && Date.now() - lastTouchTime > 450) {
 199            // It doesn't rely on `event.preventDefault()` to prevent scrolling
 200            // because the scroll event can't be canceled, so it resets the
 201            // position instead.
 202            window.scrollTo(state.scrollLeftReset, state.scrollTopReset);
 203          }
 204        }
 205      }
 206    },
 207    callbacks: {
 208      setOverlayStyles() {
 209        if (!state.overlayEnabled) {
 210          return;
 211        }
 212        let {
 213          naturalWidth,
 214          naturalHeight,
 215          offsetWidth: originalWidth,
 216          offsetHeight: originalHeight
 217        } = state.currentImage.imageRef;
 218        let {
 219          x: screenPosX,
 220          y: screenPosY
 221        } = state.currentImage.imageRef.getBoundingClientRect();
 222  
 223        // Natural ratio of the image clicked to open the lightbox.
 224        const naturalRatio = naturalWidth / naturalHeight;
 225        // Original ratio of the image clicked to open the lightbox.
 226        let originalRatio = originalWidth / originalHeight;
 227  
 228        // If it has object-fit: contain, recalculates the original sizes
 229        // and the screen position without the blank spaces.
 230        if (state.currentImage.scaleAttr === 'contain') {
 231          if (naturalRatio > originalRatio) {
 232            const heightWithoutSpace = originalWidth / naturalRatio;
 233            // Recalculates screen position without the top space.
 234            screenPosY += (originalHeight - heightWithoutSpace) / 2;
 235            originalHeight = heightWithoutSpace;
 236          } else {
 237            const widthWithoutSpace = originalHeight * naturalRatio;
 238            // Recalculates screen position without the left space.
 239            screenPosX += (originalWidth - widthWithoutSpace) / 2;
 240            originalWidth = widthWithoutSpace;
 241          }
 242        }
 243        originalRatio = originalWidth / originalHeight;
 244  
 245        // Typically, it uses the image's full-sized dimensions. If those
 246        // dimensions have not been set (i.e. an external image with only one
 247        // size), the image's dimensions in the lightbox are the same
 248        // as those of the image in the content.
 249        let imgMaxWidth = parseFloat(state.currentImage.targetWidth !== 'none' ? state.currentImage.targetWidth : naturalWidth);
 250        let imgMaxHeight = parseFloat(state.currentImage.targetHeight !== 'none' ? state.currentImage.targetHeight : naturalHeight);
 251  
 252        // Ratio of the biggest image stored in the database.
 253        let imgRatio = imgMaxWidth / imgMaxHeight;
 254        let containerMaxWidth = imgMaxWidth;
 255        let containerMaxHeight = imgMaxHeight;
 256        let containerWidth = imgMaxWidth;
 257        let containerHeight = imgMaxHeight;
 258  
 259        // Checks if the target image has a different ratio than the original
 260        // one (thumbnail). Recalculates the width and height.
 261        if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) {
 262          if (naturalRatio > imgRatio) {
 263            // If the width is reached before the height, it keeps the maxWidth
 264            // and recalculates the height unless the difference between the
 265            // maxHeight and the reducedHeight is higher than the maxWidth,
 266            // where it keeps the reducedHeight and recalculate the width.
 267            const reducedHeight = imgMaxWidth / naturalRatio;
 268            if (imgMaxHeight - reducedHeight > imgMaxWidth) {
 269              imgMaxHeight = reducedHeight;
 270              imgMaxWidth = reducedHeight * naturalRatio;
 271            } else {
 272              imgMaxHeight = imgMaxWidth / naturalRatio;
 273            }
 274          } else {
 275            // If the height is reached before the width, it keeps the maxHeight
 276            // and recalculate the width unlesss the difference between the
 277            // maxWidth and the reducedWidth is higher than the maxHeight, where
 278            // it keeps the reducedWidth and recalculate the height.
 279            const reducedWidth = imgMaxHeight * naturalRatio;
 280            if (imgMaxWidth - reducedWidth > imgMaxHeight) {
 281              imgMaxWidth = reducedWidth;
 282              imgMaxHeight = reducedWidth / naturalRatio;
 283            } else {
 284              imgMaxWidth = imgMaxHeight * naturalRatio;
 285            }
 286          }
 287          containerWidth = imgMaxWidth;
 288          containerHeight = imgMaxHeight;
 289          imgRatio = imgMaxWidth / imgMaxHeight;
 290  
 291          // Calculates the max size of the container.
 292          if (originalRatio > imgRatio) {
 293            containerMaxWidth = imgMaxWidth;
 294            containerMaxHeight = containerMaxWidth / originalRatio;
 295          } else {
 296            containerMaxHeight = imgMaxHeight;
 297            containerMaxWidth = containerMaxHeight * originalRatio;
 298          }
 299        }
 300  
 301        // If the image has been pixelated on purpose, it keeps that size.
 302        if (originalWidth > containerWidth || originalHeight > containerHeight) {
 303          containerWidth = originalWidth;
 304          containerHeight = originalHeight;
 305        }
 306  
 307        // Calculates the final lightbox image size and the scale factor.
 308        // MaxWidth is either the window container (accounting for padding) or
 309        // the image resolution.
 310        let horizontalPadding = 0;
 311        if (window.innerWidth > 480) {
 312          horizontalPadding = 80;
 313        } else if (window.innerWidth > 1920) {
 314          horizontalPadding = 160;
 315        }
 316        const verticalPadding = 80;
 317        const targetMaxWidth = Math.min(window.innerWidth - horizontalPadding, containerWidth);
 318        const targetMaxHeight = Math.min(window.innerHeight - verticalPadding, containerHeight);
 319        const targetContainerRatio = targetMaxWidth / targetMaxHeight;
 320        if (originalRatio > targetContainerRatio) {
 321          // If targetMaxWidth is reached before targetMaxHeight.
 322          containerWidth = targetMaxWidth;
 323          containerHeight = containerWidth / originalRatio;
 324        } else {
 325          // If targetMaxHeight is reached before targetMaxWidth.
 326          containerHeight = targetMaxHeight;
 327          containerWidth = containerHeight * originalRatio;
 328        }
 329        const containerScale = originalWidth / containerWidth;
 330        const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth);
 331        const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight);
 332  
 333        // As of this writing, using the calculations above will render the
 334        // lightbox with a small, erroneous whitespace on the left side of the
 335        // image in iOS Safari, perhaps due to an inconsistency in how browsers
 336        // handle absolute positioning and CSS transformation. In any case,
 337        // adding 1 pixel to the container width and height solves the problem,
 338        // though this can be removed if the issue is fixed in the future.
 339        state.overlayStyles = `
 340                  :root {
 341                      --wp--lightbox-initial-top-position: $screenPosY}px;
 342                      --wp--lightbox-initial-left-position: $screenPosX}px;
 343                      --wp--lightbox-container-width: $containerWidth + 1}px;
 344                      --wp--lightbox-container-height: $containerHeight + 1}px;
 345                      --wp--lightbox-image-width: $lightboxImgWidth}px;
 346                      --wp--lightbox-image-height: $lightboxImgHeight}px;
 347                      --wp--lightbox-scale: $containerScale};
 348                      --wp--lightbox-scrollbar-width: $window.innerWidth - document.documentElement.clientWidth}px;
 349                  }
 350              `;
 351      },
 352      setButtonStyles() {
 353        const {
 354          imageId
 355        } = (0,interactivity_namespaceObject.getContext)();
 356        const {
 357          ref
 358        } = (0,interactivity_namespaceObject.getElement)();
 359        state.metadata[imageId].imageRef = ref;
 360        state.metadata[imageId].currentSrc = ref.currentSrc;
 361        const {
 362          naturalWidth,
 363          naturalHeight,
 364          offsetWidth,
 365          offsetHeight
 366        } = ref;
 367  
 368        // If the image isn't loaded yet, it can't calculate where the button
 369        // should be.
 370        if (naturalWidth === 0 || naturalHeight === 0) {
 371          return;
 372        }
 373        const figure = ref.parentElement;
 374        const figureWidth = ref.parentElement.clientWidth;
 375  
 376        // It needs special handling for the height because a caption will cause
 377        // the figure to be taller than the image, which means it needs to
 378        // account for that when calculating the placement of the button in the
 379        // top right corner of the image.
 380        let figureHeight = ref.parentElement.clientHeight;
 381        const caption = figure.querySelector('figcaption');
 382        if (caption) {
 383          const captionComputedStyle = window.getComputedStyle(caption);
 384          if (!['absolute', 'fixed'].includes(captionComputedStyle.position)) {
 385            figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom);
 386          }
 387        }
 388        const buttonOffsetTop = figureHeight - offsetHeight;
 389        const buttonOffsetRight = figureWidth - offsetWidth;
 390        let imageButtonTop = buttonOffsetTop + 16;
 391        let imageButtonRight = buttonOffsetRight + 16;
 392  
 393        // In the case of an image with object-fit: contain, the size of the
 394        // <img> element can be larger than the image itself, so it needs to
 395        // calculate where to place the button.
 396        if (state.metadata[imageId].scaleAttr === 'contain') {
 397          // Natural ratio of the image.
 398          const naturalRatio = naturalWidth / naturalHeight;
 399          // Offset ratio of the image.
 400          const offsetRatio = offsetWidth / offsetHeight;
 401          if (naturalRatio >= offsetRatio) {
 402            // If it reaches the width first, it keeps the width and compute the
 403            // height.
 404            const referenceHeight = offsetWidth / naturalRatio;
 405            imageButtonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16;
 406            imageButtonRight = buttonOffsetRight + 16;
 407          } else {
 408            // If it reaches the height first, it keeps the height and compute
 409            // the width.
 410            const referenceWidth = offsetHeight * naturalRatio;
 411            imageButtonTop = buttonOffsetTop + 16;
 412            imageButtonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16;
 413          }
 414        }
 415        state.metadata[imageId].imageButtonTop = imageButtonTop;
 416        state.metadata[imageId].imageButtonRight = imageButtonRight;
 417      },
 418      setOverlayFocus() {
 419        if (state.overlayEnabled) {
 420          // Moves the focus to the dialog when it opens.
 421          const {
 422            ref
 423          } = (0,interactivity_namespaceObject.getElement)();
 424          ref.focus();
 425        }
 426      },
 427      initTriggerButton() {
 428        const {
 429          imageId
 430        } = (0,interactivity_namespaceObject.getContext)();
 431        const {
 432          ref
 433        } = (0,interactivity_namespaceObject.getElement)();
 434        state.metadata[imageId].buttonRef = ref;
 435      }
 436    }
 437  }, {
 438    lock: true
 439  });
 440  


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref