UNPKG

@wordpress/block-library

Version:
8 lines (7 loc) 27.4 kB
{ "version": 3, "sources": ["../../src/image/view.js"], "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {\n\tstore,\n\tgetContext,\n\tgetElement,\n\twithSyncEvent,\n\twithScope,\n} from '@wordpress/interactivity';\n\n/**\n * Internal dependencies\n */\nimport { IMAGE_PRELOAD_DELAY } from './constants';\n\n/**\n * Tracks whether user is touching screen; used to differentiate behavior for\n * touch and mouse input.\n *\n * @type {boolean}\n */\nlet isTouching = false;\n\n/**\n * Tracks the last time the screen was touched; used to differentiate behavior\n * for touch and mouse input.\n *\n * @type {number}\n */\nlet lastTouchTime = 0;\n\n/**\n * Returns the appropriate src URL for an image.\n *\n * @param {string} uploadedSrc - Full size image src.\n * @return {string} The source URL.\n */\nfunction getImageSrc( { uploadedSrc } ) {\n\treturn (\n\t\tuploadedSrc ||\n\t\t'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='\n\t);\n}\n\n/**\n * Returns the appropriate srcset for an image.\n *\n * @param {string} lightboxSrcset - Image srcset.\n * @return {string} The srcset value.\n */\nfunction getImageSrcset( { lightboxSrcset } ) {\n\treturn lightboxSrcset || '';\n}\n\nconst { state, actions, callbacks } = store(\n\t'core/image',\n\t{\n\t\tstate: {\n\t\t\tcurrentImageId: null,\n\t\t\tpreloadTimers: new Map(),\n\t\t\tpreloadedImageIds: new Set(),\n\t\t\tget currentImage() {\n\t\t\t\treturn state.metadata[ state.currentImageId ];\n\t\t\t},\n\t\t\tget overlayOpened() {\n\t\t\t\treturn state.currentImageId !== null;\n\t\t\t},\n\t\t\tget roleAttribute() {\n\t\t\t\treturn state.overlayOpened ? 'dialog' : null;\n\t\t\t},\n\t\t\tget ariaModal() {\n\t\t\t\treturn state.overlayOpened ? 'true' : null;\n\t\t\t},\n\t\t\tget enlargedSrc() {\n\t\t\t\treturn getImageSrc( state.currentImage );\n\t\t\t},\n\t\t\tget enlargedSrcset() {\n\t\t\t\treturn getImageSrcset( state.currentImage );\n\t\t\t},\n\t\t\tget figureStyles() {\n\t\t\t\treturn (\n\t\t\t\t\tstate.overlayOpened &&\n\t\t\t\t\t`${ state.currentImage.figureStyles?.replace(\n\t\t\t\t\t\t/margin[^;]*;?/g,\n\t\t\t\t\t\t''\n\t\t\t\t\t) };`\n\t\t\t\t);\n\t\t\t},\n\t\t\tget imgStyles() {\n\t\t\t\treturn (\n\t\t\t\t\tstate.overlayOpened &&\n\t\t\t\t\t`${ state.currentImage.imgStyles?.replace(\n\t\t\t\t\t\t/;$/,\n\t\t\t\t\t\t''\n\t\t\t\t\t) }; object-fit:cover;`\n\t\t\t\t);\n\t\t\t},\n\t\t\tget imageButtonRight() {\n\t\t\t\tconst { imageId } = getContext();\n\t\t\t\treturn state.metadata[ imageId ].imageButtonRight;\n\t\t\t},\n\t\t\tget imageButtonTop() {\n\t\t\t\tconst { imageId } = getContext();\n\t\t\t\treturn state.metadata[ imageId ].imageButtonTop;\n\t\t\t},\n\t\t\tget isContentHidden() {\n\t\t\t\tconst ctx = getContext();\n\t\t\t\treturn (\n\t\t\t\t\tstate.overlayEnabled && state.currentImageId === ctx.imageId\n\t\t\t\t);\n\t\t\t},\n\t\t\tget isContentVisible() {\n\t\t\t\tconst ctx = getContext();\n\t\t\t\treturn (\n\t\t\t\t\t! state.overlayEnabled &&\n\t\t\t\t\tstate.currentImageId === ctx.imageId\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t\tactions: {\n\t\t\tshowLightbox() {\n\t\t\t\tconst { imageId } = getContext();\n\n\t\t\t\t// Bails out if the image has not loaded yet.\n\t\t\t\tif ( ! state.metadata[ imageId ].imageRef?.complete ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Stores the positions of the scroll to fix it until the overlay is\n\t\t\t\t// closed.\n\t\t\t\tstate.scrollTopReset = document.documentElement.scrollTop;\n\t\t\t\tstate.scrollLeftReset = document.documentElement.scrollLeft;\n\n\t\t\t\t// Sets the current expanded image in the state and enables the overlay.\n\t\t\t\tstate.overlayEnabled = true;\n\t\t\t\tstate.currentImageId = imageId;\n\n\t\t\t\t// Computes the styles of the overlay for the animation.\n\t\t\t\tcallbacks.setOverlayStyles();\n\t\t\t},\n\t\t\thideLightbox() {\n\t\t\t\tif ( state.overlayEnabled ) {\n\t\t\t\t\tstate.overlayEnabled = false;\n\n\t\t\t\t\t// Waits until the close animation has completed before allowing a\n\t\t\t\t\t// user to scroll again. The duration of this animation is defined in\n\t\t\t\t\t// the `styles.scss` file, but in any case we should wait a few\n\t\t\t\t\t// milliseconds longer than the duration, otherwise a user may scroll\n\t\t\t\t\t// too soon and cause the animation to look sloppy.\n\t\t\t\t\tsetTimeout( function () {\n\t\t\t\t\t\t// Delays before changing the focus. Otherwise the focus ring will\n\t\t\t\t\t\t// appear on Firefox before the image has finished animating, which\n\t\t\t\t\t\t// looks broken.\n\t\t\t\t\t\tstate.currentImage.buttonRef.focus( {\n\t\t\t\t\t\t\tpreventScroll: true,\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t// Resets the current image id to mark the overlay as closed.\n\t\t\t\t\t\tstate.currentImageId = null;\n\t\t\t\t\t}, 450 );\n\t\t\t\t}\n\t\t\t},\n\t\t\thandleKeydown: withSyncEvent( ( event ) => {\n\t\t\t\tif ( state.overlayEnabled ) {\n\t\t\t\t\t// Focuses the close button when the user presses the tab key.\n\t\t\t\t\tif ( event.key === 'Tab' ) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tconst { ref } = getElement();\n\t\t\t\t\t\tref.querySelector( 'button' ).focus();\n\t\t\t\t\t}\n\t\t\t\t\t// Closes the lightbox when the user presses the escape key.\n\t\t\t\t\tif ( event.key === 'Escape' ) {\n\t\t\t\t\t\tactions.hideLightbox();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} ),\n\t\t\thandleTouchMove: withSyncEvent( ( event ) => {\n\t\t\t\t// On mobile devices, prevents triggering the scroll event because\n\t\t\t\t// otherwise the page jumps around when it resets the scroll position.\n\t\t\t\t// This also means that closing the lightbox requires that a user\n\t\t\t\t// perform a simple tap. This may be changed in the future if there is a\n\t\t\t\t// better alternative to override or reset the scroll position during\n\t\t\t\t// swipe actions.\n\t\t\t\tif ( state.overlayEnabled ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t} ),\n\t\t\thandleTouchStart() {\n\t\t\t\tisTouching = true;\n\t\t\t},\n\t\t\thandleTouchEnd() {\n\t\t\t\t// Waits a few milliseconds before resetting to ensure that pinch to\n\t\t\t\t// zoom works consistently on mobile devices when the lightbox is open.\n\t\t\t\tlastTouchTime = Date.now();\n\t\t\t\tisTouching = false;\n\t\t\t},\n\t\t\thandleScroll() {\n\t\t\t\t// Prevents scrolling behaviors that trigger content shift while the\n\t\t\t\t// lightbox is open. It would be better to accomplish through CSS alone,\n\t\t\t\t// but using overflow: hidden is currently the only way to do so and\n\t\t\t\t// that causes a layout to shift and prevents the zoom animation from\n\t\t\t\t// working in some cases because it's not possible to account for the\n\t\t\t\t// layout shift when doing the animation calculations. Instead, it uses\n\t\t\t\t// JavaScript to prevent and reset the scrolling behavior.\n\t\t\t\tif ( state.overlayOpened ) {\n\t\t\t\t\t// Avoids overriding the scroll behavior on mobile devices because\n\t\t\t\t\t// doing so breaks the pinch to zoom functionality, and users should\n\t\t\t\t\t// be able to zoom in further on the high-res image.\n\t\t\t\t\tif ( ! isTouching && Date.now() - lastTouchTime > 450 ) {\n\t\t\t\t\t\t// It doesn't rely on `event.preventDefault()` to prevent scrolling\n\t\t\t\t\t\t// because the scroll event can't be canceled, so it resets the\n\t\t\t\t\t\t// position instead.\n\t\t\t\t\t\twindow.scrollTo(\n\t\t\t\t\t\t\tstate.scrollLeftReset,\n\t\t\t\t\t\t\tstate.scrollTopReset\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tpreloadImage() {\n\t\t\t\tconst { imageId } = getContext();\n\n\t\t\t\t// Bails if it has already been preloaded. This could help\n\t\t\t\t// prevent unnecessary preloading of the same image multiple times,\n\t\t\t\t// leading to duplicate link elements in the document head.\n\t\t\t\tif ( state.preloadedImageIds.has( imageId ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Link element to preload the image.\n\t\t\t\tconst imageMetadata = state.metadata[ imageId ];\n\t\t\t\tconst imageLink = document.createElement( 'link' );\n\t\t\t\timageLink.rel = 'preload';\n\t\t\t\timageLink.as = 'image';\n\t\t\t\timageLink.href = getImageSrc( imageMetadata );\n\n\t\t\t\t// Apply srcset if available for responsive preloading\n\t\t\t\tconst srcset = getImageSrcset( imageMetadata );\n\t\t\t\tif ( srcset ) {\n\t\t\t\t\timageLink.setAttribute( 'imagesrcset', srcset );\n\t\t\t\t\timageLink.setAttribute( 'imagesizes', '100vw' );\n\t\t\t\t}\n\n\t\t\t\tdocument.head.appendChild( imageLink );\n\t\t\t\tstate.preloadedImageIds.add( imageId );\n\t\t\t},\n\t\t\tpreloadImageWithDelay() {\n\t\t\t\tconst { imageId } = getContext();\n\n\t\t\t\tactions.cancelPreload();\n\n\t\t\t\t// Set a new timer to preload the image after a short delay.\n\t\t\t\tconst timerId = setTimeout(\n\t\t\t\t\twithScope( () => {\n\t\t\t\t\t\tactions.preloadImage();\n\t\t\t\t\t\tstate.preloadTimers.delete( imageId );\n\t\t\t\t\t} ),\n\t\t\t\t\tIMAGE_PRELOAD_DELAY\n\t\t\t\t);\n\t\t\t\tstate.preloadTimers.set( imageId, timerId );\n\t\t\t},\n\t\t\tcancelPreload() {\n\t\t\t\tconst { imageId } = getContext();\n\t\t\t\tif ( state.preloadTimers.has( imageId ) ) {\n\t\t\t\t\tclearTimeout( state.preloadTimers.get( imageId ) );\n\t\t\t\t\tstate.preloadTimers.delete( imageId );\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tcallbacks: {\n\t\t\tsetOverlayStyles() {\n\t\t\t\tif ( ! state.overlayEnabled ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet {\n\t\t\t\t\tnaturalWidth,\n\t\t\t\t\tnaturalHeight,\n\t\t\t\t\toffsetWidth: originalWidth,\n\t\t\t\t\toffsetHeight: originalHeight,\n\t\t\t\t} = state.currentImage.imageRef;\n\t\t\t\tlet { x: screenPosX, y: screenPosY } =\n\t\t\t\t\tstate.currentImage.imageRef.getBoundingClientRect();\n\n\t\t\t\t// Natural ratio of the image clicked to open the lightbox.\n\t\t\t\tconst naturalRatio = naturalWidth / naturalHeight;\n\t\t\t\t// Original ratio of the image clicked to open the lightbox.\n\t\t\t\tlet originalRatio = originalWidth / originalHeight;\n\n\t\t\t\t// If it has object-fit: contain, recalculates the original sizes\n\t\t\t\t// and the screen position without the blank spaces.\n\t\t\t\tif ( state.currentImage.scaleAttr === 'contain' ) {\n\t\t\t\t\tif ( naturalRatio > originalRatio ) {\n\t\t\t\t\t\tconst heightWithoutSpace = originalWidth / naturalRatio;\n\t\t\t\t\t\t// Recalculates screen position without the top space.\n\t\t\t\t\t\tscreenPosY +=\n\t\t\t\t\t\t\t( originalHeight - heightWithoutSpace ) / 2;\n\t\t\t\t\t\toriginalHeight = heightWithoutSpace;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst widthWithoutSpace = originalHeight * naturalRatio;\n\t\t\t\t\t\t// Recalculates screen position without the left space.\n\t\t\t\t\t\tscreenPosX += ( originalWidth - widthWithoutSpace ) / 2;\n\t\t\t\t\t\toriginalWidth = widthWithoutSpace;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toriginalRatio = originalWidth / originalHeight;\n\n\t\t\t\t// Typically, it uses the image's full-sized dimensions. If those\n\t\t\t\t// dimensions have not been set (i.e. an external image with only one\n\t\t\t\t// size), the image's dimensions in the lightbox are the same\n\t\t\t\t// as those of the image in the content.\n\t\t\t\tlet imgMaxWidth = parseFloat(\n\t\t\t\t\tstate.currentImage.targetWidth !== 'none'\n\t\t\t\t\t\t? state.currentImage.targetWidth\n\t\t\t\t\t\t: naturalWidth\n\t\t\t\t);\n\t\t\t\tlet imgMaxHeight = parseFloat(\n\t\t\t\t\tstate.currentImage.targetHeight !== 'none'\n\t\t\t\t\t\t? state.currentImage.targetHeight\n\t\t\t\t\t\t: naturalHeight\n\t\t\t\t);\n\n\t\t\t\t// Ratio of the biggest image stored in the database.\n\t\t\t\tlet imgRatio = imgMaxWidth / imgMaxHeight;\n\t\t\t\tlet containerMaxWidth = imgMaxWidth;\n\t\t\t\tlet containerMaxHeight = imgMaxHeight;\n\t\t\t\tlet containerWidth = imgMaxWidth;\n\t\t\t\tlet containerHeight = imgMaxHeight;\n\n\t\t\t\t// Checks if the target image has a different ratio than the original\n\t\t\t\t// one (thumbnail). Recalculates the width and height.\n\t\t\t\tif ( naturalRatio.toFixed( 2 ) !== imgRatio.toFixed( 2 ) ) {\n\t\t\t\t\tif ( naturalRatio > imgRatio ) {\n\t\t\t\t\t\t// If the width is reached before the height, it keeps the maxWidth\n\t\t\t\t\t\t// and recalculates the height unless the difference between the\n\t\t\t\t\t\t// maxHeight and the reducedHeight is higher than the maxWidth,\n\t\t\t\t\t\t// where it keeps the reducedHeight and recalculate the width.\n\t\t\t\t\t\tconst reducedHeight = imgMaxWidth / naturalRatio;\n\t\t\t\t\t\tif ( imgMaxHeight - reducedHeight > imgMaxWidth ) {\n\t\t\t\t\t\t\timgMaxHeight = reducedHeight;\n\t\t\t\t\t\t\timgMaxWidth = reducedHeight * naturalRatio;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\timgMaxHeight = imgMaxWidth / naturalRatio;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the height is reached before the width, it keeps the maxHeight\n\t\t\t\t\t\t// and recalculate the width unlesss the difference between the\n\t\t\t\t\t\t// maxWidth and the reducedWidth is higher than the maxHeight, where\n\t\t\t\t\t\t// it keeps the reducedWidth and recalculate the height.\n\t\t\t\t\t\tconst reducedWidth = imgMaxHeight * naturalRatio;\n\t\t\t\t\t\tif ( imgMaxWidth - reducedWidth > imgMaxHeight ) {\n\t\t\t\t\t\t\timgMaxWidth = reducedWidth;\n\t\t\t\t\t\t\timgMaxHeight = reducedWidth / naturalRatio;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\timgMaxWidth = imgMaxHeight * naturalRatio;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontainerWidth = imgMaxWidth;\n\t\t\t\t\tcontainerHeight = imgMaxHeight;\n\t\t\t\t\timgRatio = imgMaxWidth / imgMaxHeight;\n\n\t\t\t\t\t// Calculates the max size of the container.\n\t\t\t\t\tif ( originalRatio > imgRatio ) {\n\t\t\t\t\t\tcontainerMaxWidth = imgMaxWidth;\n\t\t\t\t\t\tcontainerMaxHeight = containerMaxWidth / originalRatio;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontainerMaxHeight = imgMaxHeight;\n\t\t\t\t\t\tcontainerMaxWidth = containerMaxHeight * originalRatio;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If the image has been pixelated on purpose, it keeps that size.\n\t\t\t\tif (\n\t\t\t\t\toriginalWidth > containerWidth ||\n\t\t\t\t\toriginalHeight > containerHeight\n\t\t\t\t) {\n\t\t\t\t\tcontainerWidth = originalWidth;\n\t\t\t\t\tcontainerHeight = originalHeight;\n\t\t\t\t}\n\n\t\t\t\t// Calculates the final lightbox image size and the scale factor.\n\t\t\t\t// MaxWidth is either the window container (accounting for padding) or\n\t\t\t\t// the image resolution.\n\t\t\t\tlet horizontalPadding = 0;\n\t\t\t\tif ( window.innerWidth > 480 ) {\n\t\t\t\t\thorizontalPadding = 80;\n\t\t\t\t} else if ( window.innerWidth > 1920 ) {\n\t\t\t\t\thorizontalPadding = 160;\n\t\t\t\t}\n\t\t\t\tconst verticalPadding = 80;\n\n\t\t\t\tconst targetMaxWidth = Math.min(\n\t\t\t\t\twindow.innerWidth - horizontalPadding,\n\t\t\t\t\tcontainerWidth\n\t\t\t\t);\n\t\t\t\tconst targetMaxHeight = Math.min(\n\t\t\t\t\twindow.innerHeight - verticalPadding,\n\t\t\t\t\tcontainerHeight\n\t\t\t\t);\n\t\t\t\tconst targetContainerRatio = targetMaxWidth / targetMaxHeight;\n\n\t\t\t\tif ( originalRatio > targetContainerRatio ) {\n\t\t\t\t\t// If targetMaxWidth is reached before targetMaxHeight.\n\t\t\t\t\tcontainerWidth = targetMaxWidth;\n\t\t\t\t\tcontainerHeight = containerWidth / originalRatio;\n\t\t\t\t} else {\n\t\t\t\t\t// If targetMaxHeight is reached before targetMaxWidth.\n\t\t\t\t\tcontainerHeight = targetMaxHeight;\n\t\t\t\t\tcontainerWidth = containerHeight * originalRatio;\n\t\t\t\t}\n\n\t\t\t\tconst containerScale = originalWidth / containerWidth;\n\t\t\t\tconst lightboxImgWidth =\n\t\t\t\t\timgMaxWidth * ( containerWidth / containerMaxWidth );\n\t\t\t\tconst lightboxImgHeight =\n\t\t\t\t\timgMaxHeight * ( containerHeight / containerMaxHeight );\n\n\t\t\t\t// As of this writing, using the calculations above will render the\n\t\t\t\t// lightbox with a small, erroneous whitespace on the left side of the\n\t\t\t\t// image in iOS Safari, perhaps due to an inconsistency in how browsers\n\t\t\t\t// handle absolute positioning and CSS transformation. In any case,\n\t\t\t\t// adding 1 pixel to the container width and height solves the problem,\n\t\t\t\t// though this can be removed if the issue is fixed in the future.\n\t\t\t\tstate.overlayStyles = `\n\t\t\t\t\t--wp--lightbox-initial-top-position: ${ screenPosY }px;\n\t\t\t\t\t--wp--lightbox-initial-left-position: ${ screenPosX }px;\n\t\t\t\t\t--wp--lightbox-container-width: ${ containerWidth + 1 }px;\n\t\t\t\t\t--wp--lightbox-container-height: ${ containerHeight + 1 }px;\n\t\t\t\t\t--wp--lightbox-image-width: ${ lightboxImgWidth }px;\n\t\t\t\t\t--wp--lightbox-image-height: ${ lightboxImgHeight }px;\n\t\t\t\t\t--wp--lightbox-scale: ${ containerScale };\n\t\t\t\t\t--wp--lightbox-scrollbar-width: ${\n\t\t\t\t\t\twindow.innerWidth - document.documentElement.clientWidth\n\t\t\t\t\t}px;\n\t\t\t\t`;\n\t\t\t},\n\t\t\tsetButtonStyles() {\n\t\t\t\tconst { ref } = getElement();\n\n\t\t\t\t// This guard prevents errors in images with the `srcset`\n\t\t\t\t// attribute, which can dispatch `load` events even after DOM\n\t\t\t\t// removal. Preact doesn't automatically clean up `load` event\n\t\t\t\t// listeners on unmounted `img` elements (see\n\t\t\t\t// https://github.com/preactjs/preact/issues/3141).\n\t\t\t\tif ( ! ref ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { imageId } = getContext();\n\n\t\t\t\tstate.metadata[ imageId ].imageRef = ref;\n\t\t\t\tstate.metadata[ imageId ].currentSrc = ref.currentSrc;\n\n\t\t\t\tconst {\n\t\t\t\t\tnaturalWidth,\n\t\t\t\t\tnaturalHeight,\n\t\t\t\t\toffsetWidth,\n\t\t\t\t\toffsetHeight,\n\t\t\t\t} = ref;\n\n\t\t\t\t// If the image isn't loaded yet, it can't calculate where the button\n\t\t\t\t// should be.\n\t\t\t\tif ( naturalWidth === 0 || naturalHeight === 0 ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst figure = ref.parentElement;\n\t\t\t\tconst figureWidth = ref.parentElement.clientWidth;\n\n\t\t\t\t// It needs special handling for the height because a caption will cause\n\t\t\t\t// the figure to be taller than the image, which means it needs to\n\t\t\t\t// account for that when calculating the placement of the button in the\n\t\t\t\t// top right corner of the image.\n\t\t\t\tlet figureHeight = ref.parentElement.clientHeight;\n\t\t\t\tconst caption = figure.querySelector( 'figcaption' );\n\t\t\t\tif ( caption ) {\n\t\t\t\t\tconst captionComputedStyle =\n\t\t\t\t\t\twindow.getComputedStyle( caption );\n\t\t\t\t\tif (\n\t\t\t\t\t\t! [ 'absolute', 'fixed' ].includes(\n\t\t\t\t\t\t\tcaptionComputedStyle.position\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tfigureHeight =\n\t\t\t\t\t\t\tfigureHeight -\n\t\t\t\t\t\t\tcaption.offsetHeight -\n\t\t\t\t\t\t\tparseFloat( captionComputedStyle.marginTop ) -\n\t\t\t\t\t\t\tparseFloat( captionComputedStyle.marginBottom );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst buttonOffsetTop = figureHeight - offsetHeight;\n\t\t\t\tconst buttonOffsetRight = figureWidth - offsetWidth;\n\n\t\t\t\tlet imageButtonTop = buttonOffsetTop + 16;\n\t\t\t\tlet imageButtonRight = buttonOffsetRight + 16;\n\n\t\t\t\t// In the case of an image with object-fit: contain, the size of the\n\t\t\t\t// <img> element can be larger than the image itself, so it needs to\n\t\t\t\t// calculate where to place the button.\n\t\t\t\tif ( state.metadata[ imageId ].scaleAttr === 'contain' ) {\n\t\t\t\t\t// Natural ratio of the image.\n\t\t\t\t\tconst naturalRatio = naturalWidth / naturalHeight;\n\t\t\t\t\t// Offset ratio of the image.\n\t\t\t\t\tconst offsetRatio = offsetWidth / offsetHeight;\n\n\t\t\t\t\tif ( naturalRatio >= offsetRatio ) {\n\t\t\t\t\t\t// If it reaches the width first, it keeps the width and compute the\n\t\t\t\t\t\t// height.\n\t\t\t\t\t\tconst referenceHeight = offsetWidth / naturalRatio;\n\t\t\t\t\t\timageButtonTop =\n\t\t\t\t\t\t\t( offsetHeight - referenceHeight ) / 2 +\n\t\t\t\t\t\t\tbuttonOffsetTop +\n\t\t\t\t\t\t\t16;\n\t\t\t\t\t\timageButtonRight = buttonOffsetRight + 16;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If it reaches the height first, it keeps the height and compute\n\t\t\t\t\t\t// the width.\n\t\t\t\t\t\tconst referenceWidth = offsetHeight * naturalRatio;\n\t\t\t\t\t\timageButtonTop = buttonOffsetTop + 16;\n\t\t\t\t\t\timageButtonRight =\n\t\t\t\t\t\t\t( offsetWidth - referenceWidth ) / 2 +\n\t\t\t\t\t\t\tbuttonOffsetRight +\n\t\t\t\t\t\t\t16;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstate.metadata[ imageId ].imageButtonTop = imageButtonTop;\n\t\t\t\tstate.metadata[ imageId ].imageButtonRight = imageButtonRight;\n\t\t\t},\n\t\t\tsetOverlayFocus() {\n\t\t\t\tif ( state.overlayEnabled ) {\n\t\t\t\t\t// Moves the focus to the dialog when it opens.\n\t\t\t\t\tconst { ref } = getElement();\n\t\t\t\t\tref.focus();\n\t\t\t\t}\n\t\t\t},\n\t\t\tinitTriggerButton() {\n\t\t\t\tconst { imageId } = getContext();\n\t\t\t\tconst { ref } = getElement();\n\t\t\t\tstate.metadata[ imageId ].buttonRef = ref;\n\t\t\t},\n\t\t},\n\t},\n\t{ lock: true }\n);\n"], "mappings": "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", "names": [] }