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