UNPKG

@shopgate/pwa-common

Version:

Common library for the Shopgate Connect PWA.

40 lines 6.41 kB
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);