@shopgate/pwa-common
Version:
Common library for the Shopgate Connect PWA.
40 lines • 6.41 kB
JavaScript
function _extends(){_extends=Object.assign||function(target){for(var i=1;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(Object.prototype.hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;};return _extends.apply(this,arguments);}function _slicedToArray(arr,i){return _arrayWithHoles(arr)||_iterableToArrayLimit(arr,i)||_nonIterableRest();}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance");}function _iterableToArrayLimit(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally{try{if(!_n&&_i["return"]!=null)_i["return"]();}finally{if(_d)throw _e;}}return _arr;}function _arrayWithHoles(arr){if(Array.isArray(arr))return arr;}import React,{useMemo,useState,useEffect,useRef,useCallback,memo}from'react';import PropTypes from'prop-types';import classNames from'classnames';import noop from'lodash/noop';import{themeConfig}from'@shopgate/engage';import{getFullImageSource}from'@shopgate/engage/core/helpers';import styles from"./style";import ImageInner from"./ImageInner";var themeColors=themeConfig.colors;/**
* Calculates the Greatest Common Divisor (GCD) of two numbers using the Euclidean algorithm.
*
* @param {number} a - The first number (must be a positive integer).
* @param {number} b - The second number (must be a positive integer).
* @returns {number} The greatest common divisor of `a` and `b`.
*
* @example
* gcd(1920, 1080); // Returns 120
* gcd(10, 15); // Returns 5
* gcd(100, 25); // Returns 25
*/var gcd=function gcd(a,b){return b===0?a:gcd(b,a%b);};/**
* The image component.
* @param {Object} props The components props.
* @returns {JSX.Element}
*/var Image=function Image(_ref){var alt=_ref.alt,backgroundColor=_ref.backgroundColor,className=_ref.className,classNameImg=_ref.classNameImg,parentRendersPlaceholder=_ref.forcePlaceholder,highestResolutionLoaded=_ref.highestResolutionLoaded,onError=_ref.onError,onLoad=_ref.onLoad,ratio=_ref.ratio,resolutions=_ref.resolutions,src=_ref.src,lazy=_ref.lazy,unwrapped=_ref.unwrapped;// Prepare two image sources - a small preview image and a large main image. The idea is to
// display an image as soon as possible. Small images might be also available in the cache from
// the previous page.
var sources=useMemo(function(){// Create a preview source when resolutions array has more than one element
var preview=resolutions.length>1?getFullImageSource(src,resolutions[resolutions.length-2]):null;// Create a main source when resolutions array has at least one element (highest resolution)
var main=resolutions.length>0?getFullImageSource(src,resolutions[resolutions.length-1]):null;return{// Only assign preview source if it is different from the main source. Image swap logic
// will not run when no preview source is available.
preview:preview!==main?preview:null,main:main};},[resolutions,src]);var imgRef=useRef(null);var _useState=useState(!lazy),_useState2=_slicedToArray(_useState,2),isInView=_useState2[0],setIsInView=_useState2[1];// Effect to create an Intersection Observer to enable lazy loading of preview images
useEffect(function(){if(!lazy||!sources.preview)return undefined;// Intersection Observer to check if the image is in (or near) the viewport
var observer=new IntersectionObserver(function(_ref2){var _ref3=_slicedToArray(_ref2,1),entry=_ref3[0];if(entry.isIntersecting){setIsInView(true);// stop observing once visible
observer.unobserve(entry.target);}},// load a bit earlier
{rootMargin:'100px'});if(imgRef.current){// start observing the image element
observer.observe(imgRef.current);}return function(){// disconnect the observer when the component is unmounted
observer.disconnect();};},[lazy,sources.preview]);/**
* Handles the onLoad event of the image.
*/var handleOnLoad=useCallback(function(e){highestResolutionLoaded();onLoad(e);},[highestResolutionLoaded,onLoad]);/**
* Handles the onError event of the image.
*/var handleOnError=useCallback(function(e){onError(e);},[onError]);/**
* Memoized calculation of aspect ratio and CSS padding-hack ratio for responsive elements.
*
* Returns n object containing:
* - `aspectRatio` {string} - The aspect ratio in the format `width / height` (e.g., `16 / 9`).
* - `paddingHackRatio` {string} - The CSS padding-hack ratio as a percentage for older browsers
* (e.g., `56.250%` for a 16:9 ratio).
*/var _useMemo=useMemo(function(){var width;var height;if(ratio){var _ratio=_slicedToArray(ratio,2);width=_ratio[0];height=_ratio[1];}else{var _resolutions=resolutions[resolutions.length-1];width=_resolutions.width;height=_resolutions.height;}var divisor=gcd(width,height);return{aspectRatio:"".concat(width/divisor," / ").concat(height/divisor),paddingHackRatio:"".concat((height/width*100).toFixed(3),"%")};},[ratio,resolutions]),aspectRatio=_useMemo.aspectRatio,paddingHackRatio=_useMemo.paddingHackRatio;if(unwrapped){if(!(src&&!parentRendersPlaceholder))return null;return React.createElement(ImageInner,{ref:imgRef,src:sources.main,className:classNames(classNameImg),style:_extends({aspectRatio:aspectRatio},isInView&&sources.preview&&{backgroundImage:"url(".concat(sources.preview,")"),backgroundSize:'contain',backgroundRepeat:'no-repeat',backgroundPosition:'center'}),alt:alt,lazy:lazy,onLoad:handleOnLoad,onError:handleOnError});}var containerStyle=styles.container(backgroundColor,paddingHackRatio);return React.createElement("div",{className:classNames(containerStyle,className,'common__image__container')},src&&!parentRendersPlaceholder&&React.createElement(ImageInner,{ref:imgRef,src:sources.main,className:classNames(classNameImg),style:_extends({aspectRatio:aspectRatio},isInView&&sources.preview&&{backgroundImage:"url(".concat(sources.preview,")"),backgroundSize:'contain',backgroundRepeat:'no-repeat',backgroundPosition:'center'}),alt:alt,lazy:lazy,onLoad:handleOnLoad,onError:handleOnError}));};var defaultResolutions=[{width:440,height:440}];Image.defaultProps={alt:null,backgroundColor:themeColors.placeholder,className:'',classNameImg:'',forcePlaceholder:false,highestResolutionLoaded:noop,onError:noop,onLoad:noop,ratio:null,resolutions:defaultResolutions,src:null,unwrapped:false,lazy:true};export default memo(Image);