[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/blocks/ -> image.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  /******/ !function() {
   8  /******/     // define getter functions for harmony exports
   9  /******/     __webpack_require__.d = function(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  /******/ !function() {
  20  /******/     __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
  21  /******/ }();
  22  /******/ 
  23  /************************************************************************/
  24  var __webpack_exports__ = {};
  25  
  26  ;// CONCATENATED MODULE: external "@wordpress/interactivity"
  27  var x = y => { var x = {}; __webpack_require__.d(x, y); return x; }
  28  var y = x => () => x
  29  var 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 });
  30  ;// CONCATENATED MODULE: ./node_modules/@wordpress/block-library/build-module/image/view.js
  31  /**
  32   * WordPress dependencies
  33   */
  34  
  35  const focusableSelectors = ['a[href]', 'area[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 'select:not([disabled]):not([aria-hidden])', 'textarea:not([disabled]):not([aria-hidden])', 'button:not([disabled]):not([aria-hidden])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
  36  
  37  /**
  38   * Stores a context-bound scroll handler.
  39   *
  40   * This callback could be defined inline inside of the store
  41   * object but it's created externally to avoid confusion about
  42   * how its logic is called. This logic is not referenced directly
  43   * by the directives in the markup because the scroll event we
  44   * need to listen to is triggered on the window; so by defining it
  45   * outside of the store, we signal that the behavior here is different.
  46   * If we find a compelling reason to move it to the store, feel free.
  47   *
  48   * @type {Function}
  49   */
  50  let scrollCallback;
  51  
  52  /**
  53   * Tracks whether user is touching screen; used to
  54   * differentiate behavior for touch and mouse input.
  55   *
  56   * @type {boolean}
  57   */
  58  let isTouching = false;
  59  
  60  /**
  61   * Tracks the last time the screen was touched; used to
  62   * differentiate behavior for touch and mouse input.
  63   *
  64   * @type {number}
  65   */
  66  let lastTouchTime = 0;
  67  
  68  /**
  69   * Lightbox page-scroll handler: prevents scrolling.
  70   *
  71   * This handler is added to prevent scrolling behaviors that
  72   * trigger content shift while the lightbox is open.
  73   *
  74   * It would be better to accomplish this through CSS alone, but
  75   * using overflow: hidden is currently the only way to do so, and
  76   * that causes the layout to shift and prevents the zoom animation
  77   * from working in some cases because we're unable to account for
  78   * the layout shift when doing the animation calculations. Instead,
  79   * here we use JavaScript to prevent and reset the scrolling
  80   * behavior. In the future, we may be able to use CSS or overflow: hidden
  81   * instead to not rely on JavaScript, but this seems to be the best approach
  82   * for now that provides the best visual experience.
  83   *
  84   * @param {Object} ctx Context object with the `core/image` namespace.
  85   */
  86  function handleScroll(ctx) {
  87    // We can't override the scroll behavior on mobile devices
  88    // because doing so breaks the pinch to zoom functionality, and we
  89    // want to allow users to zoom in further on the high-res image.
  90    if (!isTouching && Date.now() - lastTouchTime > 450) {
  91      // We are unable to use event.preventDefault() to prevent scrolling
  92      // because the scroll event can't be canceled, so we reset the position instead.
  93      window.scrollTo(ctx.scrollLeftReset, ctx.scrollTopReset);
  94    }
  95  }
  96  const {
  97    state,
  98    actions,
  99    callbacks
 100  } = (0,interactivity_namespaceObject.store)('core/image', {
 101    state: {
 102      windowWidth: window.innerWidth,
 103      windowHeight: window.innerHeight,
 104      get roleAttribute() {
 105        const ctx = (0,interactivity_namespaceObject.getContext)();
 106        return ctx.lightboxEnabled ? 'dialog' : null;
 107      },
 108      get ariaModal() {
 109        const ctx = (0,interactivity_namespaceObject.getContext)();
 110        return ctx.lightboxEnabled ? 'true' : null;
 111      },
 112      get dialogLabel() {
 113        const ctx = (0,interactivity_namespaceObject.getContext)();
 114        return ctx.lightboxEnabled ? ctx.dialogLabel : null;
 115      },
 116      get lightboxObjectFit() {
 117        const ctx = (0,interactivity_namespaceObject.getContext)();
 118        if (ctx.initialized) {
 119          return 'cover';
 120        }
 121      },
 122      get enlargedImgSrc() {
 123        const ctx = (0,interactivity_namespaceObject.getContext)();
 124        return ctx.initialized ? ctx.imageUploadedSrc : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
 125      }
 126    },
 127    actions: {
 128      showLightbox(event) {
 129        const ctx = (0,interactivity_namespaceObject.getContext)();
 130        // We can't initialize the lightbox until the reference
 131        // image is loaded, otherwise the UX is broken.
 132        if (!ctx.imageLoaded) {
 133          return;
 134        }
 135        ctx.initialized = true;
 136        ctx.lastFocusedElement = window.document.activeElement;
 137        ctx.scrollDelta = 0;
 138        ctx.pointerType = event.pointerType;
 139        ctx.lightboxEnabled = true;
 140        setStyles(ctx, ctx.imageRef);
 141        ctx.scrollTopReset = window.pageYOffset || document.documentElement.scrollTop;
 142  
 143        // In most cases, this value will be 0, but this is included
 144        // in case a user has created a page with horizontal scrolling.
 145        ctx.scrollLeftReset = window.pageXOffset || document.documentElement.scrollLeft;
 146  
 147        // We define and bind the scroll callback here so
 148        // that we can pass the context and as an argument.
 149        // We may be able to change this in the future if we
 150        // define the scroll callback in the store instead, but
 151        // this approach seems to tbe clearest for now.
 152        scrollCallback = handleScroll.bind(null, ctx);
 153  
 154        // We need to add a scroll event listener to the window
 155        // here because we are unable to otherwise access it via
 156        // the Interactivity API directives. If we add a native way
 157        // to access the window, we can remove this.
 158        window.addEventListener('scroll', scrollCallback, false);
 159      },
 160      hideLightbox() {
 161        const ctx = (0,interactivity_namespaceObject.getContext)();
 162        ctx.hideAnimationEnabled = true;
 163        if (ctx.lightboxEnabled) {
 164          // We want to wait until the close animation is completed
 165          // before allowing a user to scroll again. The duration of this
 166          // animation is defined in the styles.scss and depends on if the
 167          // animation is 'zoom' or 'fade', but in any case we should wait
 168          // a few milliseconds longer than the duration, otherwise a user
 169          // may scroll too soon and cause the animation to look sloppy.
 170          setTimeout(function () {
 171            window.removeEventListener('scroll', scrollCallback);
 172            // If we don't delay before changing the focus,
 173            // the focus ring will appear on Firefox before
 174            // the image has finished animating, which looks broken.
 175            ctx.lightboxTriggerRef.focus({
 176              preventScroll: true
 177            });
 178          }, 450);
 179          ctx.lightboxEnabled = false;
 180        }
 181      },
 182      handleKeydown(event) {
 183        const ctx = (0,interactivity_namespaceObject.getContext)();
 184        if (ctx.lightboxEnabled) {
 185          if (event.key === 'Tab' || event.keyCode === 9) {
 186            // If shift + tab it change the direction
 187            if (event.shiftKey && window.document.activeElement === ctx.firstFocusableElement) {
 188              event.preventDefault();
 189              ctx.lastFocusableElement.focus();
 190            } else if (!event.shiftKey && window.document.activeElement === ctx.lastFocusableElement) {
 191              event.preventDefault();
 192              ctx.firstFocusableElement.focus();
 193            }
 194          }
 195          if (event.key === 'Escape' || event.keyCode === 27) {
 196            actions.hideLightbox(event);
 197          }
 198        }
 199      },
 200      // This is fired just by lazily loaded
 201      // images on the page, not all images.
 202      handleLoad() {
 203        const ctx = (0,interactivity_namespaceObject.getContext)();
 204        const {
 205          ref
 206        } = (0,interactivity_namespaceObject.getElement)();
 207        ctx.imageLoaded = true;
 208        ctx.imageCurrentSrc = ref.currentSrc;
 209        callbacks.setButtonStyles();
 210      },
 211      handleTouchStart() {
 212        isTouching = true;
 213      },
 214      handleTouchMove(event) {
 215        const ctx = (0,interactivity_namespaceObject.getContext)();
 216        // On mobile devices, we want to prevent triggering the
 217        // scroll event because otherwise the page jumps around as
 218        // we reset the scroll position. This also means that closing
 219        // the lightbox requires that a user perform a simple tap. This
 220        // may be changed in the future if we find a better alternative
 221        // to override or reset the scroll position during swipe actions.
 222        if (ctx.lightboxEnabled) {
 223          event.preventDefault();
 224        }
 225      },
 226      handleTouchEnd() {
 227        // We need to wait a few milliseconds before resetting
 228        // to ensure that pinch to zoom works consistently
 229        // on mobile devices when the lightbox is open.
 230        lastTouchTime = Date.now();
 231        isTouching = false;
 232      }
 233    },
 234    callbacks: {
 235      initOriginImage() {
 236        const ctx = (0,interactivity_namespaceObject.getContext)();
 237        const {
 238          ref
 239        } = (0,interactivity_namespaceObject.getElement)();
 240        ctx.imageRef = ref;
 241        if (ref.complete) {
 242          ctx.imageLoaded = true;
 243          ctx.imageCurrentSrc = ref.currentSrc;
 244        }
 245      },
 246      initTriggerButton() {
 247        const ctx = (0,interactivity_namespaceObject.getContext)();
 248        const {
 249          ref
 250        } = (0,interactivity_namespaceObject.getElement)();
 251        ctx.lightboxTriggerRef = ref;
 252      },
 253      initLightbox() {
 254        const ctx = (0,interactivity_namespaceObject.getContext)();
 255        const {
 256          ref
 257        } = (0,interactivity_namespaceObject.getElement)();
 258        if (ctx.lightboxEnabled) {
 259          const focusableElements = ref.querySelectorAll(focusableSelectors);
 260          ctx.firstFocusableElement = focusableElements[0];
 261          ctx.lastFocusableElement = focusableElements[focusableElements.length - 1];
 262  
 263          // Move focus to the dialog when opening it.
 264          ref.focus();
 265        }
 266      },
 267      setButtonStyles() {
 268        const {
 269          ref
 270        } = (0,interactivity_namespaceObject.getElement)();
 271        const {
 272          naturalWidth,
 273          naturalHeight,
 274          offsetWidth,
 275          offsetHeight
 276        } = ref;
 277  
 278        // If the image isn't loaded yet, we can't
 279        // calculate where the button should be.
 280        if (naturalWidth === 0 || naturalHeight === 0) {
 281          return;
 282        }
 283        const figure = ref.parentElement;
 284        const figureWidth = ref.parentElement.clientWidth;
 285  
 286        // We need special handling for the height because
 287        // a caption will cause the figure to be taller than
 288        // the image, which means we need to account for that
 289        // when calculating the placement of the button in the
 290        // top right corner of the image.
 291        let figureHeight = ref.parentElement.clientHeight;
 292        const caption = figure.querySelector('figcaption');
 293        if (caption) {
 294          const captionComputedStyle = window.getComputedStyle(caption);
 295          if (!['absolute', 'fixed'].includes(captionComputedStyle.position)) {
 296            figureHeight = figureHeight - caption.offsetHeight - parseFloat(captionComputedStyle.marginTop) - parseFloat(captionComputedStyle.marginBottom);
 297          }
 298        }
 299        const buttonOffsetTop = figureHeight - offsetHeight;
 300        const buttonOffsetRight = figureWidth - offsetWidth;
 301        const ctx = (0,interactivity_namespaceObject.getContext)();
 302  
 303        // In the case of an image with object-fit: contain, the
 304        // size of the <img> element can be larger than the image itself,
 305        // so we need to calculate where to place the button.
 306        if (ctx.scaleAttr === 'contain') {
 307          // Natural ratio of the image.
 308          const naturalRatio = naturalWidth / naturalHeight;
 309          // Offset ratio of the image.
 310          const offsetRatio = offsetWidth / offsetHeight;
 311          if (naturalRatio >= offsetRatio) {
 312            // If it reaches the width first, keep
 313            // the width and compute the height.
 314            const referenceHeight = offsetWidth / naturalRatio;
 315            ctx.imageButtonTop = (offsetHeight - referenceHeight) / 2 + buttonOffsetTop + 16;
 316            ctx.imageButtonRight = buttonOffsetRight + 16;
 317          } else {
 318            // If it reaches the height first, keep
 319            // the height and compute the width.
 320            const referenceWidth = offsetHeight * naturalRatio;
 321            ctx.imageButtonTop = buttonOffsetTop + 16;
 322            ctx.imageButtonRight = (offsetWidth - referenceWidth) / 2 + buttonOffsetRight + 16;
 323          }
 324        } else {
 325          ctx.imageButtonTop = buttonOffsetTop + 16;
 326          ctx.imageButtonRight = buttonOffsetRight + 16;
 327        }
 328      },
 329      setStylesOnResize() {
 330        const ctx = (0,interactivity_namespaceObject.getContext)();
 331        const {
 332          ref
 333        } = (0,interactivity_namespaceObject.getElement)();
 334        if (ctx.lightboxEnabled && (state.windowWidth || state.windowHeight)) {
 335          setStyles(ctx, ref);
 336        }
 337      }
 338    }
 339  });
 340  window.addEventListener('resize', debounce(() => {
 341    state.windowWidth = window.innerWidth;
 342    state.windowHeight = window.innerHeight;
 343  }));
 344  
 345  /**
 346   * Computes styles for the lightbox and adds them to the document.
 347   *
 348   * @function
 349   * @param {Object} ctx - Context for the `core/image` namespace.
 350   * @param {Object} ref - The element reference.
 351   */
 352  function setStyles(ctx, ref) {
 353    // The reference img element lies adjacent
 354    // to the event target button in the DOM.
 355    let {
 356      naturalWidth,
 357      naturalHeight,
 358      offsetWidth: originalWidth,
 359      offsetHeight: originalHeight
 360    } = ref;
 361    let {
 362      x: screenPosX,
 363      y: screenPosY
 364    } = ref.getBoundingClientRect();
 365  
 366    // Natural ratio of the image clicked to open the lightbox.
 367    const naturalRatio = naturalWidth / naturalHeight;
 368    // Original ratio of the image clicked to open the lightbox.
 369    let originalRatio = originalWidth / originalHeight;
 370  
 371    // If it has object-fit: contain, recalculate the original sizes
 372    // and the screen position without the blank spaces.
 373    if (ctx.scaleAttr === 'contain') {
 374      if (naturalRatio > originalRatio) {
 375        const heightWithoutSpace = originalWidth / naturalRatio;
 376        // Recalculate screen position without the top space.
 377        screenPosY += (originalHeight - heightWithoutSpace) / 2;
 378        originalHeight = heightWithoutSpace;
 379      } else {
 380        const widthWithoutSpace = originalHeight * naturalRatio;
 381        // Recalculate screen position without the left space.
 382        screenPosX += (originalWidth - widthWithoutSpace) / 2;
 383        originalWidth = widthWithoutSpace;
 384      }
 385    }
 386    originalRatio = originalWidth / originalHeight;
 387  
 388    // Typically, we use the image's full-sized dimensions. If those
 389    // dimensions have not been set (i.e. an external image with only one size),
 390    // the image's dimensions in the lightbox are the same
 391    // as those of the image in the content.
 392    let imgMaxWidth = parseFloat(ctx.targetWidth !== 'none' ? ctx.targetWidth : naturalWidth);
 393    let imgMaxHeight = parseFloat(ctx.targetHeight !== 'none' ? ctx.targetHeight : naturalHeight);
 394  
 395    // Ratio of the biggest image stored in the database.
 396    let imgRatio = imgMaxWidth / imgMaxHeight;
 397    let containerMaxWidth = imgMaxWidth;
 398    let containerMaxHeight = imgMaxHeight;
 399    let containerWidth = imgMaxWidth;
 400    let containerHeight = imgMaxHeight;
 401    // Check if the target image has a different ratio than the original one (thumbnail).
 402    // Recalculate the width and height.
 403    if (naturalRatio.toFixed(2) !== imgRatio.toFixed(2)) {
 404      if (naturalRatio > imgRatio) {
 405        // If the width is reached before the height, we keep the maxWidth
 406        // and recalculate the height.
 407        // Unless the difference between the maxHeight and the reducedHeight
 408        // is higher than the maxWidth, where we keep the reducedHeight and
 409        // recalculate the width.
 410        const reducedHeight = imgMaxWidth / naturalRatio;
 411        if (imgMaxHeight - reducedHeight > imgMaxWidth) {
 412          imgMaxHeight = reducedHeight;
 413          imgMaxWidth = reducedHeight * naturalRatio;
 414        } else {
 415          imgMaxHeight = imgMaxWidth / naturalRatio;
 416        }
 417      } else {
 418        // If the height is reached before the width, we keep the maxHeight
 419        // and recalculate the width.
 420        // Unless the difference between the maxWidth and the reducedWidth
 421        // is higher than the maxHeight, where we keep the reducedWidth and
 422        // recalculate the height.
 423        const reducedWidth = imgMaxHeight * naturalRatio;
 424        if (imgMaxWidth - reducedWidth > imgMaxHeight) {
 425          imgMaxWidth = reducedWidth;
 426          imgMaxHeight = reducedWidth / naturalRatio;
 427        } else {
 428          imgMaxWidth = imgMaxHeight * naturalRatio;
 429        }
 430      }
 431      containerWidth = imgMaxWidth;
 432      containerHeight = imgMaxHeight;
 433      imgRatio = imgMaxWidth / imgMaxHeight;
 434  
 435      // Calculate the max size of the container.
 436      if (originalRatio > imgRatio) {
 437        containerMaxWidth = imgMaxWidth;
 438        containerMaxHeight = containerMaxWidth / originalRatio;
 439      } else {
 440        containerMaxHeight = imgMaxHeight;
 441        containerMaxWidth = containerMaxHeight * originalRatio;
 442      }
 443    }
 444  
 445    // If the image has been pixelated on purpose, keep that size.
 446    if (originalWidth > containerWidth || originalHeight > containerHeight) {
 447      containerWidth = originalWidth;
 448      containerHeight = originalHeight;
 449    }
 450  
 451    // Calculate the final lightbox image size and the
 452    // scale factor. MaxWidth is either the window container
 453    // (accounting for padding) or the image resolution.
 454    let horizontalPadding = 0;
 455    if (window.innerWidth > 480) {
 456      horizontalPadding = 80;
 457    } else if (window.innerWidth > 1920) {
 458      horizontalPadding = 160;
 459    }
 460    const verticalPadding = 80;
 461    const targetMaxWidth = Math.min(window.innerWidth - horizontalPadding, containerWidth);
 462    const targetMaxHeight = Math.min(window.innerHeight - verticalPadding, containerHeight);
 463    const targetContainerRatio = targetMaxWidth / targetMaxHeight;
 464    if (originalRatio > targetContainerRatio) {
 465      // If targetMaxWidth is reached before targetMaxHeight
 466      containerWidth = targetMaxWidth;
 467      containerHeight = containerWidth / originalRatio;
 468    } else {
 469      // If targetMaxHeight is reached before targetMaxWidth
 470      containerHeight = targetMaxHeight;
 471      containerWidth = containerHeight * originalRatio;
 472    }
 473    const containerScale = originalWidth / containerWidth;
 474    const lightboxImgWidth = imgMaxWidth * (containerWidth / containerMaxWidth);
 475    const lightboxImgHeight = imgMaxHeight * (containerHeight / containerMaxHeight);
 476  
 477    // Add the CSS variables needed.
 478    let styleTag = document.getElementById('wp-lightbox-styles');
 479    if (!styleTag) {
 480      styleTag = document.createElement('style');
 481      styleTag.id = 'wp-lightbox-styles';
 482      document.head.appendChild(styleTag);
 483    }
 484  
 485    // As of this writing, using the calculations above will render the lightbox
 486    // with a small, erroneous whitespace on the left side of the image in iOS Safari,
 487    // perhaps due to an inconsistency in how browsers handle absolute positioning and CSS
 488    // transformation. In any case, adding 1 pixel to the container width and height solves
 489    // the problem, though this can be removed if the issue is fixed in the future.
 490    styleTag.innerHTML = `
 491          :root {
 492              --wp--lightbox-initial-top-position: $screenPosY}px;
 493              --wp--lightbox-initial-left-position: $screenPosX}px;
 494              --wp--lightbox-container-width: $containerWidth + 1}px;
 495              --wp--lightbox-container-height: $containerHeight + 1}px;
 496              --wp--lightbox-image-width: $lightboxImgWidth}px;
 497              --wp--lightbox-image-height: $lightboxImgHeight}px;
 498              --wp--lightbox-scale: $containerScale};
 499              --wp--lightbox-scrollbar-width: $window.innerWidth - document.documentElement.clientWidth}px;
 500          }
 501      `;
 502  }
 503  
 504  /**
 505   * Debounces a function call.
 506   *
 507   * @function
 508   * @param {Function} func - A function to be called
 509   * @param {number}   wait - The time to wait before calling the function
 510   */
 511  function debounce(func, wait = 50) {
 512    let timeout;
 513    return () => {
 514      const later = () => {
 515        timeout = null;
 516        func();
 517      };
 518      clearTimeout(timeout);
 519      timeout = setTimeout(later, wait);
 520    };
 521  }


Generated : Wed Jan 31 08:20:02 2024 Cross-referenced by PHPXref