@lumx/react 2.1.9-alpha-thumbnail17 → 2.1.9-alpha-thumbnail18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,9 +57,9 @@ function shiftPosition(scale, focusPoint, imageSize, containerSize) {
57
57
  var startFocus = scaledFocusHeight - containerSize / 2;
58
58
  var shift = startFocus / (scaledSize - containerSize);
59
59
  return Math.floor(Math.max(Math.min(shift, 1), 0) * 100);
60
- } // Compute CSS properties to apply the focus point.
61
-
60
+ }
62
61
 
62
+ // Compute CSS properties to apply the focus point.
63
63
  var useFocusPointStyle = function useFocusPointStyle(_ref, element, isLoaded) {
64
64
  var image = _ref.image,
65
65
  aspectRatio = _ref.aspectRatio,
@@ -70,6 +70,7 @@ var useFocusPointStyle = function useFocusPointStyle(_ref, element, isLoaded) {
70
70
  height = _ref$imgProps.height;
71
71
  // Get natural image size from imgProps or img element.
72
72
  var imageSize = useMemo(function () {
73
+ // Focus point is not applicable => exit early
73
74
  if (!image || aspectRatio === AspectRatio.original || !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) && !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y)) return undefined;
74
75
  if (typeof width === 'number' && typeof height === 'number') return {
75
76
  width: width,
@@ -80,36 +81,74 @@ var useFocusPointStyle = function useFocusPointStyle(_ref, element, isLoaded) {
80
81
  height: element.naturalHeight
81
82
  };
82
83
  return undefined;
83
- }, [aspectRatio, element, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y, height, image, isLoaded, width]); // Get container size.
84
-
85
- var containerWidth = element === null || element === void 0 ? void 0 : element.offsetWidth;
86
- var containerHeight = element === null || element === void 0 ? void 0 : element.offsetHeight; // Compute focus point CSS style.
87
-
88
- return useMemo(function () {
89
- if (!image || aspectRatio === AspectRatio.original || !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) && !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y)) return {};
90
-
91
- if (containerWidth && containerHeight && imageSize) {
92
- var heightScale = imageSize.height / containerHeight;
93
- var widthScale = imageSize.width / containerWidth;
94
- var scale = Math.min(widthScale, heightScale); // Focus Y relative to the top (instead of the center)
84
+ }, [aspectRatio, element, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y, height, image, isLoaded, width]); // Get container size (dependant on imageSize).
95
85
 
96
- var focusPointFromTop = Math.abs(((focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y) || 0) - 1) / 2;
97
- var y = shiftPosition(scale, focusPointFromTop, imageSize.height, containerHeight); // Focus X relative to the left (instead of the center)
86
+ var _useState = useState(undefined),
87
+ _useState2 = _slicedToArray(_useState, 2),
88
+ containerSize = _useState2[0],
89
+ setContainerSize = _useState2[1];
90
+
91
+ useEffect(function updateContainerSize() {
92
+ var cWidth = element === null || element === void 0 ? void 0 : element.offsetWidth;
93
+ var cHeight = element === null || element === void 0 ? void 0 : element.offsetHeight;
94
+
95
+ if (cWidth && cHeight) {
96
+ // Update only if needed.
97
+ setContainerSize(function (oldContainerSize) {
98
+ return (oldContainerSize === null || oldContainerSize === void 0 ? void 0 : oldContainerSize.width) === cWidth && oldContainerSize.height === cHeight ? oldContainerSize : {
99
+ width: cWidth,
100
+ height: cHeight
101
+ };
102
+ });
103
+ } else if (imageSize) {
104
+ // Wait for a render (in case the container size is dependent on the image size).
105
+ requestAnimationFrame(updateContainerSize);
106
+ }
107
+ }, [element === null || element === void 0 ? void 0 : element.offsetHeight, element === null || element === void 0 ? void 0 : element.offsetWidth, imageSize]); // Compute style.
108
+
109
+ var _useState3 = useState({}),
110
+ _useState4 = _slicedToArray(_useState3, 2),
111
+ style = _useState4[0],
112
+ setStyle = _useState4[1];
98
113
 
99
- var focusPointFromLeft = Math.abs(((focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) || 0) + 1) / 2;
100
- var x = shiftPosition(scale, focusPointFromLeft, imageSize.width, containerWidth);
101
- console.debug('x, y', aspectRatio, x, y);
102
- return {
103
- objectPosition: "".concat(x, "% ").concat(y, "%")
114
+ useEffect(function () {
115
+ // Focus point is not applicable => exit early
116
+ if (!image || aspectRatio === AspectRatio.original || !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) && !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y)) {
117
+ return;
118
+ }
119
+
120
+ if (!element || !imageSize) {
121
+ // Focus point can be computed but now right now (image size unknown).
122
+ setStyle({
123
+ visibility: 'hidden'
124
+ });
125
+ return;
126
+ }
127
+
128
+ if (!containerSize) {
129
+ // Missing container size abort focus point compute.
130
+ setStyle({});
131
+ return;
132
+ }
133
+
134
+ var heightScale = imageSize.height / containerSize.height;
135
+ var widthScale = imageSize.width / containerSize.width;
136
+ var scale = Math.min(widthScale, heightScale); // Focus Y relative to the top (instead of the center)
137
+
138
+ var focusPointFromTop = Math.abs(((focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y) || 0) - 1) / 2;
139
+ var y = shiftPosition(scale, focusPointFromTop, imageSize.height, containerSize.height); // Focus X relative to the left (instead of the center)
140
+
141
+ var focusPointFromLeft = Math.abs(((focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) || 0) + 1) / 2;
142
+ var x = shiftPosition(scale, focusPointFromLeft, imageSize.width, containerSize.width);
143
+ var objectPosition = "".concat(x, "% ").concat(y, "%"); // Update only if needed.
144
+
145
+ setStyle(function (oldStyle) {
146
+ return oldStyle.objectPosition === objectPosition ? oldStyle : {
147
+ objectPosition: objectPosition
104
148
  };
105
- } // Focus point can't be computed yet => We hide the image until it can.
106
-
107
-
108
- return {
109
- visibility: 'hidden'
110
- };
111
- }, // Update when the image changes (url or size), when the ratio changes, when the container size changes and when the focus point changes.
112
- [image, imageSize, aspectRatio, containerHeight, containerWidth, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y]);
149
+ });
150
+ }, [aspectRatio, containerSize, element, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y, image, imageSize]);
151
+ return style;
113
152
  };
114
153
 
115
154
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Thumbnail2.js","sources":["../../../src/components/thumbnail/useImageLoad.ts","../../../src/components/thumbnail/useFocusPointStyle.tsx","../../../src/components/thumbnail/Thumbnail.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nexport type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';\n\nfunction getState(img: HTMLImageElement | null | undefined, event?: Event) {\n // Error event occurred or image loaded empty.\n if (event?.type === 'error' || (img?.complete && (img?.naturalWidth === 0 || img?.naturalHeight === 0))) {\n return 'hasError';\n }\n // Image is undefined or incomplete.\n if (!img || !img.complete) {\n return 'isLoading';\n }\n // Else loaded.\n return 'isLoaded';\n}\n\nexport function useImageLoad(imageURL: string, imgRef?: HTMLImageElement): LoadingState {\n const [state, setState] = useState<LoadingState>(getState(imgRef));\n\n // Update state when changing image URL or DOM reference.\n useEffect(() => {\n setState(getState(imgRef));\n }, [imageURL, imgRef]);\n\n // Listen to `load` and `error` event on image\n useEffect(() => {\n const img = imgRef;\n if (!img) return undefined;\n const update = (event?: Event) => setState(getState(img, event));\n img.addEventListener('load', update);\n img.addEventListener('error', update);\n return () => {\n img.removeEventListener('load', update);\n img.removeEventListener('error', update);\n };\n }, [imgRef, imgRef?.src]);\n\n return state;\n}\n","import { CSSProperties, useMemo } from 'react';\nimport { AspectRatio } from '@lumx/react/components';\nimport { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';\n\n// Calculate shift to center the focus point in the container.\nfunction shiftPosition(scale: number, focusPoint: number, imageSize: number, containerSize: number) {\n const scaledSize = imageSize / scale;\n const scaledFocusHeight = focusPoint * scaledSize;\n const startFocus = scaledFocusHeight - containerSize / 2;\n const shift = startFocus / (scaledSize - containerSize);\n return Math.floor(Math.max(Math.min(shift, 1), 0) * 100);\n}\n\n// Compute CSS properties to apply the focus point.\nexport const useFocusPointStyle = (\n { image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,\n element: HTMLImageElement | undefined,\n isLoaded: boolean,\n): CSSProperties => {\n // Get natural image size from imgProps or img element.\n const imageSize = useMemo(() => {\n if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;\n if (typeof width === 'number' && typeof height === 'number') return { width, height };\n if (element && isLoaded) return { width: element.naturalWidth, height: element.naturalHeight };\n return undefined;\n }, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);\n\n // Get container size.\n const containerWidth = element?.offsetWidth;\n const containerHeight = element?.offsetHeight;\n\n // Compute focus point CSS style.\n return useMemo(\n () => {\n if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return {};\n if (containerWidth && containerHeight && imageSize) {\n const heightScale = imageSize.height / containerHeight;\n const widthScale = imageSize.width / containerWidth;\n const scale = Math.min(widthScale, heightScale);\n\n // Focus Y relative to the top (instead of the center)\n const focusPointFromTop = Math.abs((focusPoint?.y || 0) - 1) / 2;\n const y = shiftPosition(scale, focusPointFromTop, imageSize.height, containerHeight);\n\n // Focus X relative to the left (instead of the center)\n const focusPointFromLeft = Math.abs((focusPoint?.x || 0) + 1) / 2;\n const x = shiftPosition(scale, focusPointFromLeft, imageSize.width, containerWidth);\n\n console.debug('x, y', aspectRatio, x, y);\n return { objectPosition: `${x}% ${y}%` };\n }\n // Focus point can't be computed yet => We hide the image until it can.\n return { visibility: 'hidden' };\n },\n // Update when the image changes (url or size), when the ratio changes, when the container size changes and when the focus point changes.\n [image, imageSize, aspectRatio, containerHeight, containerWidth, focusPoint?.x, focusPoint?.y],\n );\n};\n","import React, {\n CSSProperties,\n forwardRef,\n ImgHTMLAttributes,\n KeyboardEventHandler,\n MouseEventHandler,\n ReactElement,\n ReactNode,\n Ref,\n useState,\n} from 'react';\nimport classNames from 'classnames';\n\nimport { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react';\n\nimport { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';\n\nimport { mdiImageBroken } from '@lumx/icons';\nimport { mergeRefs } from '@lumx/react/utils/mergeRefs';\nimport { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';\nimport { useFocusPointStyle } from '@lumx/react/components/thumbnail/useFocusPointStyle';\nimport { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';\n\ntype ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;\n\n/**\n * Defines the props of the component.\n */\nexport interface ThumbnailProps extends GenericProps {\n /** Alignment of the thumbnail in it's parent (requires flex parent). */\n align?: HorizontalAlignment;\n /** Image alternative text. */\n alt: string;\n /** Image aspect ratio. */\n aspectRatio?: AspectRatio;\n /** Badge. */\n badge?: ReactElement;\n /** Image cross origin resource policy. */\n crossOrigin?: ImgHTMLProps['crossOrigin'];\n /** Fallback icon (SVG path) or react node when image fails to load. */\n fallback?: string | ReactNode;\n /** Whether the thumbnail should fill it's parent size (requires flex parent) or not. */\n fillHeight?: boolean;\n /** Apply relative vertical and horizontal shift (from -1 to 1) on the image position inside the thumbnail. */\n focusPoint?: FocusPoint;\n /** Image URL. */\n image: string;\n /** Props to inject into the native <img> element. */\n imgProps?: ImgHTMLProps;\n /** Reference to the native <img> element. */\n imgRef?: Ref<HTMLImageElement>;\n /** Set to true to force the display of the loading skeleton. */\n isLoading?: boolean;\n /** Size variant of the component. */\n size?: ThumbnailSize;\n /** Image loading mode. */\n loading?: ImgHTMLProps['loading'];\n /** On click callback. */\n onClick?: MouseEventHandler<HTMLDivElement>;\n /** On key press callback. */\n onKeyPress?: KeyboardEventHandler<HTMLDivElement>;\n /** Theme adapting the component to light or dark background. */\n theme?: Theme;\n /** Variant of the component. */\n variant?: ThumbnailVariant;\n /** Props to pass to the link wrapping the thumbnail. */\n linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;\n /** Custom react component for the link (can be used to inject react router Link). */\n linkAs?: 'a' | any;\n}\n\n/**\n * Component display name.\n */\nconst COMPONENT_NAME = 'Thumbnail';\n\n/**\n * Component default class name and class prefix.\n */\nconst CLASSNAME = getRootClassName(COMPONENT_NAME);\n\n/**\n * Component default props.\n */\nconst DEFAULT_PROPS: Partial<ThumbnailProps> = {\n fallback: mdiImageBroken,\n loading: 'lazy',\n theme: Theme.light,\n};\n\n/**\n * Thumbnail component.\n *\n * @param props Component props.\n * @param ref Component ref.\n * @return React element.\n */\nexport const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {\n const {\n align,\n alt,\n aspectRatio = AspectRatio.original,\n badge,\n className,\n crossOrigin,\n fallback,\n fillHeight,\n focusPoint,\n image,\n imgProps,\n imgRef: propImgRef,\n isLoading: isLoadingProp,\n loading,\n size,\n theme,\n variant,\n linkProps,\n linkAs,\n ...forwardedProps\n } = props;\n const [imgElement, setImgElement] = useState<HTMLImageElement>();\n\n // Image loading state.\n const loadingState = useImageLoad(image, imgElement);\n const isLoaded = loadingState === 'isLoaded';\n const isLoading = isLoadingProp || loadingState === 'isLoading';\n const hasError = loadingState === 'hasError';\n\n // Focus point.\n const focusPointStyle = useFocusPointStyle(props, imgElement, isLoaded);\n\n const hasIconErrorFallback = hasError && typeof fallback === 'string';\n const hasCustomErrorFallback = hasError && !hasIconErrorFallback;\n const imageErrorStyle: CSSProperties = {};\n if (hasIconErrorFallback) {\n // Keep the image layout on icon fallback.\n imageErrorStyle.visibility = 'hidden';\n } else if (hasCustomErrorFallback) {\n // Remove the image on custom fallback.\n imageErrorStyle.display = 'none';\n }\n\n const isLink = Boolean(linkProps?.href || linkAs);\n const isButton = !!forwardedProps.onClick;\n const isClickable = isButton || isLink;\n\n let Wrapper: any = 'div';\n const wrapperProps = { ...forwardedProps };\n if (isLink) {\n Wrapper = linkAs || 'a';\n Object.assign(wrapperProps, linkProps);\n } else if (isButton) {\n Wrapper = 'button';\n }\n\n return (\n <Wrapper\n {...wrapperProps}\n ref={ref}\n className={classNames(\n linkProps?.className,\n className,\n handleBasicClasses({\n align,\n aspectRatio,\n prefix: CLASSNAME,\n size,\n theme,\n variant,\n isClickable,\n hasError,\n hasIconErrorFallback,\n hasCustomErrorFallback,\n isLoading,\n hasBadge: !!badge,\n }),\n fillHeight && `${CLASSNAME}--fill-height`,\n )}\n >\n <div className={`${CLASSNAME}__background`}>\n <img\n {...imgProps}\n style={{\n ...imgProps?.style,\n ...imageErrorStyle,\n ...focusPointStyle,\n }}\n ref={mergeRefs(setImgElement, propImgRef)}\n className={classNames(`${CLASSNAME}__image`, isLoading && `${CLASSNAME}__image--is-loading`)}\n crossOrigin={crossOrigin}\n src={image}\n alt={alt}\n loading={loading}\n />\n {!isLoading && hasError && (\n <div className={`${CLASSNAME}__fallback`}>\n {hasIconErrorFallback ? (\n <Icon icon={fallback as string} size={Size.xxs} theme={theme} />\n ) : (\n fallback\n )}\n </div>\n )}\n </div>\n {badge &&\n React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}\n </Wrapper>\n );\n});\nThumbnail.displayName = COMPONENT_NAME;\nThumbnail.className = CLASSNAME;\nThumbnail.defaultProps = DEFAULT_PROPS;\n"],"names":["getState","img","event","type","complete","naturalWidth","naturalHeight","useImageLoad","imageURL","imgRef","useState","state","setState","useEffect","undefined","update","addEventListener","removeEventListener","src","shiftPosition","scale","focusPoint","imageSize","containerSize","scaledSize","scaledFocusHeight","startFocus","shift","Math","floor","max","min","useFocusPointStyle","element","isLoaded","image","aspectRatio","imgProps","width","height","useMemo","AspectRatio","original","x","y","containerWidth","offsetWidth","containerHeight","offsetHeight","heightScale","widthScale","focusPointFromTop","abs","focusPointFromLeft","console","debug","objectPosition","visibility","COMPONENT_NAME","CLASSNAME","getRootClassName","DEFAULT_PROPS","fallback","mdiImageBroken","loading","theme","Theme","light","Thumbnail","forwardRef","props","ref","align","alt","badge","className","crossOrigin","fillHeight","propImgRef","isLoadingProp","isLoading","size","variant","linkProps","linkAs","forwardedProps","imgElement","setImgElement","loadingState","hasError","focusPointStyle","hasIconErrorFallback","hasCustomErrorFallback","imageErrorStyle","display","isLink","Boolean","href","isButton","onClick","isClickable","Wrapper","wrapperProps","Object","assign","classNames","handleBasicClasses","prefix","hasBadge","style","mergeRefs","Size","xxs","React","cloneElement","displayName","defaultProps"],"mappings":";;;;;;;;AAIA,SAASA,QAAT,CAAkBC,GAAlB,EAA4DC,KAA5D,EAA2E;AACvE;AACA,MAAI,CAAAA,KAAK,SAAL,IAAAA,KAAK,WAAL,YAAAA,KAAK,CAAEC,IAAP,MAAgB,OAAhB,IAA4B,CAAAF,GAAG,SAAH,IAAAA,GAAG,WAAH,YAAAA,GAAG,CAAEG,QAAL,MAAkB,CAAAH,GAAG,SAAH,IAAAA,GAAG,WAAH,YAAAA,GAAG,CAAEI,YAAL,MAAsB,CAAtB,IAA2B,CAAAJ,GAAG,SAAH,IAAAA,GAAG,WAAH,YAAAA,GAAG,CAAEK,aAAL,MAAuB,CAApE,CAAhC,EAAyG;AACrG,WAAO,UAAP;AACH,GAJsE;;;AAMvE,MAAI,CAACL,GAAD,IAAQ,CAACA,GAAG,CAACG,QAAjB,EAA2B;AACvB,WAAO,WAAP;AACH,GARsE;;;AAUvE,SAAO,UAAP;AACH;;AAEM,SAASG,YAAT,CAAsBC,QAAtB,EAAwCC,MAAxC,EAAiF;AAAA,kBAC1DC,QAAQ,CAAeV,QAAQ,CAACS,MAAD,CAAvB,CADkD;AAAA;AAAA,MAC7EE,KAD6E;AAAA,MACtEC,QADsE;;;AAIpFC,EAAAA,SAAS,CAAC,YAAM;AACZD,IAAAA,QAAQ,CAACZ,QAAQ,CAACS,MAAD,CAAT,CAAR;AACH,GAFQ,EAEN,CAACD,QAAD,EAAWC,MAAX,CAFM,CAAT,CAJoF;;AASpFI,EAAAA,SAAS,CAAC,YAAM;AACZ,QAAMZ,GAAG,GAAGQ,MAAZ;AACA,QAAI,CAACR,GAAL,EAAU,OAAOa,SAAP;;AACV,QAAMC,MAAM,GAAG,SAATA,MAAS,CAACb,KAAD;AAAA,aAAmBU,QAAQ,CAACZ,QAAQ,CAACC,GAAD,EAAMC,KAAN,CAAT,CAA3B;AAAA,KAAf;;AACAD,IAAAA,GAAG,CAACe,gBAAJ,CAAqB,MAArB,EAA6BD,MAA7B;AACAd,IAAAA,GAAG,CAACe,gBAAJ,CAAqB,OAArB,EAA8BD,MAA9B;AACA,WAAO,YAAM;AACTd,MAAAA,GAAG,CAACgB,mBAAJ,CAAwB,MAAxB,EAAgCF,MAAhC;AACAd,MAAAA,GAAG,CAACgB,mBAAJ,CAAwB,OAAxB,EAAiCF,MAAjC;AACH,KAHD;AAIH,GAVQ,EAUN,CAACN,MAAD,EAASA,MAAT,aAASA,MAAT,uBAASA,MAAM,CAAES,GAAjB,CAVM,CAAT;AAYA,SAAOP,KAAP;AACH;;ACnCD;AACA,SAASQ,aAAT,CAAuBC,KAAvB,EAAsCC,UAAtC,EAA0DC,SAA1D,EAA6EC,aAA7E,EAAoG;AAChG,MAAMC,UAAU,GAAGF,SAAS,GAAGF,KAA/B;AACA,MAAMK,iBAAiB,GAAGJ,UAAU,GAAGG,UAAvC;AACA,MAAME,UAAU,GAAGD,iBAAiB,GAAGF,aAAa,GAAG,CAAvD;AACA,MAAMI,KAAK,GAAGD,UAAU,IAAIF,UAAU,GAAGD,aAAjB,CAAxB;AACA,SAAOK,IAAI,CAACC,KAAL,CAAWD,IAAI,CAACE,GAAL,CAASF,IAAI,CAACG,GAAL,CAASJ,KAAT,EAAgB,CAAhB,CAAT,EAA6B,CAA7B,IAAkC,GAA7C,CAAP;AACH;;;IAGYK,kBAAkB,GAAG,SAArBA,kBAAqB,OAE9BC,OAF8B,EAG9BC,QAH8B,EAId;AAAA,MAHdC,KAGc,QAHdA,KAGc;AAAA,MAHPC,WAGO,QAHPA,WAGO;AAAA,MAHMf,UAGN,QAHMA,UAGN;AAAA,2BAHkBgB,QAGlB;AAAA,6CAHgD,EAGhD;AAAA,MAH8BC,KAG9B,iBAH8BA,KAG9B;AAAA,MAHqCC,MAGrC,iBAHqCA,MAGrC;AAChB;AACA,MAAMjB,SAAS,GAAGkB,OAAO,CAAC,YAAM;AAC5B,QAAI,CAACL,KAAD,IAAUC,WAAW,KAAKK,WAAW,CAACC,QAAtC,IAAmD,EAACrB,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAEsB,CAAb,KAAkB,EAACtB,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAEuB,CAAb,CAAzE,EAA0F,OAAO9B,SAAP;AAC1F,QAAI,OAAOwB,KAAP,KAAiB,QAAjB,IAA6B,OAAOC,MAAP,KAAkB,QAAnD,EAA6D,OAAO;AAAED,MAAAA,KAAK,EAALA,KAAF;AAASC,MAAAA,MAAM,EAANA;AAAT,KAAP;AAC7D,QAAIN,OAAO,IAAIC,QAAf,EAAyB,OAAO;AAAEI,MAAAA,KAAK,EAAEL,OAAO,CAAC5B,YAAjB;AAA+BkC,MAAAA,MAAM,EAAEN,OAAO,CAAC3B;AAA/C,KAAP;AACzB,WAAOQ,SAAP;AACH,GALwB,EAKtB,CAACsB,WAAD,EAAcH,OAAd,EAAuBZ,UAAvB,aAAuBA,UAAvB,uBAAuBA,UAAU,CAAEsB,CAAnC,EAAsCtB,UAAtC,aAAsCA,UAAtC,uBAAsCA,UAAU,CAAEuB,CAAlD,EAAqDL,MAArD,EAA6DJ,KAA7D,EAAoED,QAApE,EAA8EI,KAA9E,CALsB,CAAzB,CAFgB;;AAUhB,MAAMO,cAAc,GAAGZ,OAAH,aAAGA,OAAH,uBAAGA,OAAO,CAAEa,WAAhC;AACA,MAAMC,eAAe,GAAGd,OAAH,aAAGA,OAAH,uBAAGA,OAAO,CAAEe,YAAjC,CAXgB;;AAchB,SAAOR,OAAO,CACV,YAAM;AACF,QAAI,CAACL,KAAD,IAAUC,WAAW,KAAKK,WAAW,CAACC,QAAtC,IAAmD,EAACrB,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAEsB,CAAb,KAAkB,EAACtB,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAEuB,CAAb,CAAzE,EAA0F,OAAO,EAAP;;AAC1F,QAAIC,cAAc,IAAIE,eAAlB,IAAqCzB,SAAzC,EAAoD;AAChD,UAAM2B,WAAW,GAAG3B,SAAS,CAACiB,MAAV,GAAmBQ,eAAvC;AACA,UAAMG,UAAU,GAAG5B,SAAS,CAACgB,KAAV,GAAkBO,cAArC;AACA,UAAMzB,KAAK,GAAGQ,IAAI,CAACG,GAAL,CAASmB,UAAT,EAAqBD,WAArB,CAAd,CAHgD;;AAMhD,UAAME,iBAAiB,GAAGvB,IAAI,CAACwB,GAAL,CAAS,CAAC,CAAA/B,UAAU,SAAV,IAAAA,UAAU,WAAV,YAAAA,UAAU,CAAEuB,CAAZ,KAAiB,CAAlB,IAAuB,CAAhC,IAAqC,CAA/D;AACA,UAAMA,CAAC,GAAGzB,aAAa,CAACC,KAAD,EAAQ+B,iBAAR,EAA2B7B,SAAS,CAACiB,MAArC,EAA6CQ,eAA7C,CAAvB,CAPgD;;AAUhD,UAAMM,kBAAkB,GAAGzB,IAAI,CAACwB,GAAL,CAAS,CAAC,CAAA/B,UAAU,SAAV,IAAAA,UAAU,WAAV,YAAAA,UAAU,CAAEsB,CAAZ,KAAiB,CAAlB,IAAuB,CAAhC,IAAqC,CAAhE;AACA,UAAMA,CAAC,GAAGxB,aAAa,CAACC,KAAD,EAAQiC,kBAAR,EAA4B/B,SAAS,CAACgB,KAAtC,EAA6CO,cAA7C,CAAvB;AAEAS,MAAAA,OAAO,CAACC,KAAR,CAAc,MAAd,EAAsBnB,WAAtB,EAAmCO,CAAnC,EAAsCC,CAAtC;AACA,aAAO;AAAEY,QAAAA,cAAc,YAAKb,CAAL,eAAWC,CAAX;AAAhB,OAAP;AACH,KAjBC;;;AAmBF,WAAO;AAAEa,MAAAA,UAAU,EAAE;AAAd,KAAP;AACH,GArBS;AAuBV,GAACtB,KAAD,EAAQb,SAAR,EAAmBc,WAAnB,EAAgCW,eAAhC,EAAiDF,cAAjD,EAAiExB,UAAjE,aAAiEA,UAAjE,uBAAiEA,UAAU,CAAEsB,CAA7E,EAAgFtB,UAAhF,aAAgFA,UAAhF,uBAAgFA,UAAU,CAAEuB,CAA5F,CAvBU,CAAd;AAyBH;;ACcD;;;AAGA,IAAMc,cAAc,GAAG,WAAvB;AAEA;;;;AAGA,IAAMC,SAAS,GAAGC,gBAAgB,CAACF,cAAD,CAAlC;AAEA;;;;AAGA,IAAMG,aAAsC,GAAG;AAC3CC,EAAAA,QAAQ,EAAEC,cADiC;AAE3CC,EAAAA,OAAO,EAAE,MAFkC;AAG3CC,EAAAA,KAAK,EAAEC,KAAK,CAACC;AAH8B,CAA/C;AAMA;;;;;;;;IAOaC,SAA+B,GAAGC,UAAU,CAAC,UAACC,KAAD,EAAQC,GAAR,EAAgB;AAAA,MAElEC,KAFkE,GAsBlEF,KAtBkE,CAElEE,KAFkE;AAAA,MAGlEC,GAHkE,GAsBlEH,KAtBkE,CAGlEG,GAHkE;AAAA,2BAsBlEH,KAtBkE,CAIlElC,WAJkE;AAAA,MAIlEA,WAJkE,mCAIpDK,WAAW,CAACC,QAJwC;AAAA,MAKlEgC,KALkE,GAsBlEJ,KAtBkE,CAKlEI,KALkE;AAAA,MAMlEC,SANkE,GAsBlEL,KAtBkE,CAMlEK,SANkE;AAAA,MAOlEC,WAPkE,GAsBlEN,KAtBkE,CAOlEM,WAPkE;AAAA,MAQlEd,QARkE,GAsBlEQ,KAtBkE,CAQlER,QARkE;AAAA,MASlEe,UATkE,GAsBlEP,KAtBkE,CASlEO,UATkE;AAAA,MAUlExD,UAVkE,GAsBlEiD,KAtBkE,CAUlEjD,UAVkE;AAAA,MAWlEc,KAXkE,GAsBlEmC,KAtBkE,CAWlEnC,KAXkE;AAAA,MAYlEE,QAZkE,GAsBlEiC,KAtBkE,CAYlEjC,QAZkE;AAAA,MAa1DyC,UAb0D,GAsBlER,KAtBkE,CAalE7D,MAbkE;AAAA,MAcvDsE,aAduD,GAsBlET,KAtBkE,CAclEU,SAdkE;AAAA,MAelEhB,OAfkE,GAsBlEM,KAtBkE,CAelEN,OAfkE;AAAA,MAgBlEiB,IAhBkE,GAsBlEX,KAtBkE,CAgBlEW,IAhBkE;AAAA,MAiBlEhB,KAjBkE,GAsBlEK,KAtBkE,CAiBlEL,KAjBkE;AAAA,MAkBlEiB,OAlBkE,GAsBlEZ,KAtBkE,CAkBlEY,OAlBkE;AAAA,MAmBlEC,SAnBkE,GAsBlEb,KAtBkE,CAmBlEa,SAnBkE;AAAA,MAoBlEC,MApBkE,GAsBlEd,KAtBkE,CAoBlEc,MApBkE;AAAA,MAqB/DC,cArB+D,4BAsBlEf,KAtBkE;;AAAA,kBAuBlC5D,QAAQ,EAvB0B;AAAA;AAAA,MAuB/D4E,UAvB+D;AAAA,MAuBnDC,aAvBmD;;;AA0BtE,MAAMC,YAAY,GAAGjF,YAAY,CAAC4B,KAAD,EAAQmD,UAAR,CAAjC;AACA,MAAMpD,QAAQ,GAAGsD,YAAY,KAAK,UAAlC;AACA,MAAMR,SAAS,GAAGD,aAAa,IAAIS,YAAY,KAAK,WAApD;AACA,MAAMC,QAAQ,GAAGD,YAAY,KAAK,UAAlC,CA7BsE;;AAgCtE,MAAME,eAAe,GAAG1D,kBAAkB,CAACsC,KAAD,EAAQgB,UAAR,EAAoBpD,QAApB,CAA1C;AAEA,MAAMyD,oBAAoB,GAAGF,QAAQ,IAAI,OAAO3B,QAAP,KAAoB,QAA7D;AACA,MAAM8B,sBAAsB,GAAGH,QAAQ,IAAI,CAACE,oBAA5C;AACA,MAAME,eAA8B,GAAG,EAAvC;;AACA,MAAIF,oBAAJ,EAA0B;AACtB;AACAE,IAAAA,eAAe,CAACpC,UAAhB,GAA6B,QAA7B;AACH,GAHD,MAGO,IAAImC,sBAAJ,EAA4B;AAC/B;AACAC,IAAAA,eAAe,CAACC,OAAhB,GAA0B,MAA1B;AACH;;AAED,MAAMC,MAAM,GAAGC,OAAO,CAAC,CAAAb,SAAS,SAAT,IAAAA,SAAS,WAAT,YAAAA,SAAS,CAAEc,IAAX,KAAmBb,MAApB,CAAtB;AACA,MAAMc,QAAQ,GAAG,CAAC,CAACb,cAAc,CAACc,OAAlC;AACA,MAAMC,WAAW,GAAGF,QAAQ,IAAIH,MAAhC;AAEA,MAAIM,OAAY,GAAG,KAAnB;;AACA,MAAMC,YAAY,sBAAQjB,cAAR,CAAlB;;AACA,MAAIU,MAAJ,EAAY;AACRM,IAAAA,OAAO,GAAGjB,MAAM,IAAI,GAApB;AACAmB,IAAAA,MAAM,CAACC,MAAP,CAAcF,YAAd,EAA4BnB,SAA5B;AACH,GAHD,MAGO,IAAIe,QAAJ,EAAc;AACjBG,IAAAA,OAAO,GAAG,QAAV;AACH;;AAED,SACI,oBAAC,OAAD,eACQC,YADR;AAEI,IAAA,GAAG,EAAE/B,GAFT;AAGI,IAAA,SAAS,EAAEkC,UAAU,CACjBtB,SADiB,aACjBA,SADiB,uBACjBA,SAAS,CAAER,SADM,EAEjBA,SAFiB,EAGjB+B,kBAAkB,CAAC;AACflC,MAAAA,KAAK,EAALA,KADe;AAEfpC,MAAAA,WAAW,EAAXA,WAFe;AAGfuE,MAAAA,MAAM,EAAEhD,SAHO;AAIfsB,MAAAA,IAAI,EAAJA,IAJe;AAKfhB,MAAAA,KAAK,EAALA,KALe;AAMfiB,MAAAA,OAAO,EAAPA,OANe;AAOfkB,MAAAA,WAAW,EAAXA,WAPe;AAQfX,MAAAA,QAAQ,EAARA,QARe;AASfE,MAAAA,oBAAoB,EAApBA,oBATe;AAUfC,MAAAA,sBAAsB,EAAtBA,sBAVe;AAWfZ,MAAAA,SAAS,EAATA,SAXe;AAYf4B,MAAAA,QAAQ,EAAE,CAAC,CAAClC;AAZG,KAAD,CAHD,EAiBjBG,UAAU,cAAOlB,SAAP,kBAjBO;AAHzB,MAuBI;AAAK,IAAA,SAAS,YAAKA,SAAL;AAAd,KACI,wCACQtB,QADR;AAEI,IAAA,KAAK,qBACEA,QADF,aACEA,QADF,uBACEA,QAAQ,CAAEwE,KADZ,MAEEhB,eAFF,MAGEH,eAHF,CAFT;AAOI,IAAA,GAAG,EAAEoB,SAAS,CAACvB,aAAD,EAAgBT,UAAhB,CAPlB;AAQI,IAAA,SAAS,EAAE2B,UAAU,WAAI9C,SAAJ,cAAwBqB,SAAS,cAAOrB,SAAP,wBAAjC,CARzB;AASI,IAAA,WAAW,EAAEiB,WATjB;AAUI,IAAA,GAAG,EAAEzC,KAVT;AAWI,IAAA,GAAG,EAAEsC,GAXT;AAYI,IAAA,OAAO,EAAET;AAZb,KADJ,EAeK,CAACgB,SAAD,IAAcS,QAAd,IACG;AAAK,IAAA,SAAS,YAAK9B,SAAL;AAAd,KACKgC,oBAAoB,GACjB,oBAAC,IAAD;AAAM,IAAA,IAAI,EAAE7B,QAAZ;AAAgC,IAAA,IAAI,EAAEiD,IAAI,CAACC,GAA3C;AAAgD,IAAA,KAAK,EAAE/C;AAAvD,IADiB,GAGjBH,QAJR,CAhBR,CAvBJ,EAgDKY,KAAK,IACFuC,KAAK,CAACC,YAAN,CAAmBxC,KAAnB,EAA0B;AAAEC,IAAAA,SAAS,EAAE8B,UAAU,WAAI9C,SAAJ,cAAwBe,KAAK,CAACJ,KAAN,CAAYK,SAApC;AAAvB,GAA1B,CAjDR,CADJ;AAqDH,CA/GwD;AAgHzDP,SAAS,CAAC+C,WAAV,GAAwBzD,cAAxB;AACAU,SAAS,CAACO,SAAV,GAAsBhB,SAAtB;AACAS,SAAS,CAACgD,YAAV,GAAyBvD,aAAzB;;;;"}
1
+ {"version":3,"file":"Thumbnail2.js","sources":["../../../src/components/thumbnail/useImageLoad.ts","../../../src/components/thumbnail/useFocusPointStyle.tsx","../../../src/components/thumbnail/Thumbnail.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nexport type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';\n\nfunction getState(img: HTMLImageElement | null | undefined, event?: Event) {\n // Error event occurred or image loaded empty.\n if (event?.type === 'error' || (img?.complete && (img?.naturalWidth === 0 || img?.naturalHeight === 0))) {\n return 'hasError';\n }\n // Image is undefined or incomplete.\n if (!img || !img.complete) {\n return 'isLoading';\n }\n // Else loaded.\n return 'isLoaded';\n}\n\nexport function useImageLoad(imageURL: string, imgRef?: HTMLImageElement): LoadingState {\n const [state, setState] = useState<LoadingState>(getState(imgRef));\n\n // Update state when changing image URL or DOM reference.\n useEffect(() => {\n setState(getState(imgRef));\n }, [imageURL, imgRef]);\n\n // Listen to `load` and `error` event on image\n useEffect(() => {\n const img = imgRef;\n if (!img) return undefined;\n const update = (event?: Event) => setState(getState(img, event));\n img.addEventListener('load', update);\n img.addEventListener('error', update);\n return () => {\n img.removeEventListener('load', update);\n img.removeEventListener('error', update);\n };\n }, [imgRef, imgRef?.src]);\n\n return state;\n}\n","import { CSSProperties, useEffect, useMemo, useState } from 'react';\nimport { AspectRatio } from '@lumx/react/components';\nimport { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';\n\n// Calculate shift to center the focus point in the container.\nfunction shiftPosition(scale: number, focusPoint: number, imageSize: number, containerSize: number) {\n const scaledSize = imageSize / scale;\n const scaledFocusHeight = focusPoint * scaledSize;\n const startFocus = scaledFocusHeight - containerSize / 2;\n const shift = startFocus / (scaledSize - containerSize);\n return Math.floor(Math.max(Math.min(shift, 1), 0) * 100);\n}\n\ntype Size = { width: number; height: number };\n\n// Compute CSS properties to apply the focus point.\nexport const useFocusPointStyle = (\n { image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,\n element: HTMLImageElement | undefined,\n isLoaded: boolean,\n): CSSProperties => {\n // Get natural image size from imgProps or img element.\n const imageSize: Size | undefined = useMemo(() => {\n // Focus point is not applicable => exit early\n if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;\n if (typeof width === 'number' && typeof height === 'number') return { width, height };\n if (element && isLoaded) return { width: element.naturalWidth, height: element.naturalHeight };\n return undefined;\n }, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);\n\n // Get container size (dependant on imageSize).\n const [containerSize, setContainerSize] = useState<Size | undefined>(undefined);\n useEffect(\n function updateContainerSize() {\n const cWidth = element?.offsetWidth;\n const cHeight = element?.offsetHeight;\n if (cWidth && cHeight) {\n // Update only if needed.\n setContainerSize((oldContainerSize) =>\n oldContainerSize?.width === cWidth && oldContainerSize.height === cHeight\n ? oldContainerSize\n : { width: cWidth, height: cHeight },\n );\n } else if (imageSize) {\n // Wait for a render (in case the container size is dependent on the image size).\n requestAnimationFrame(updateContainerSize);\n }\n },\n [element?.offsetHeight, element?.offsetWidth, imageSize],\n );\n\n // Compute style.\n const [style, setStyle] = useState<CSSProperties>({});\n useEffect(() => {\n // Focus point is not applicable => exit early\n if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) {\n return;\n }\n if (!element || !imageSize) {\n // Focus point can be computed but now right now (image size unknown).\n setStyle({ visibility: 'hidden' });\n return;\n }\n if (!containerSize) {\n // Missing container size abort focus point compute.\n setStyle({});\n return;\n }\n\n const heightScale = imageSize.height / containerSize.height;\n const widthScale = imageSize.width / containerSize.width;\n const scale = Math.min(widthScale, heightScale);\n\n // Focus Y relative to the top (instead of the center)\n const focusPointFromTop = Math.abs((focusPoint?.y || 0) - 1) / 2;\n const y = shiftPosition(scale, focusPointFromTop, imageSize.height, containerSize.height);\n\n // Focus X relative to the left (instead of the center)\n const focusPointFromLeft = Math.abs((focusPoint?.x || 0) + 1) / 2;\n const x = shiftPosition(scale, focusPointFromLeft, imageSize.width, containerSize.width);\n\n const objectPosition = `${x}% ${y}%`;\n\n // Update only if needed.\n setStyle((oldStyle) => (oldStyle.objectPosition === objectPosition ? oldStyle : { objectPosition }));\n }, [aspectRatio, containerSize, element, focusPoint?.x, focusPoint?.y, image, imageSize]);\n\n return style;\n};\n","import React, {\n CSSProperties,\n forwardRef,\n ImgHTMLAttributes,\n KeyboardEventHandler,\n MouseEventHandler,\n ReactElement,\n ReactNode,\n Ref,\n useState,\n} from 'react';\nimport classNames from 'classnames';\n\nimport { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react';\n\nimport { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';\n\nimport { mdiImageBroken } from '@lumx/icons';\nimport { mergeRefs } from '@lumx/react/utils/mergeRefs';\nimport { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';\nimport { useFocusPointStyle } from '@lumx/react/components/thumbnail/useFocusPointStyle';\nimport { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';\n\ntype ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;\n\n/**\n * Defines the props of the component.\n */\nexport interface ThumbnailProps extends GenericProps {\n /** Alignment of the thumbnail in it's parent (requires flex parent). */\n align?: HorizontalAlignment;\n /** Image alternative text. */\n alt: string;\n /** Image aspect ratio. */\n aspectRatio?: AspectRatio;\n /** Badge. */\n badge?: ReactElement;\n /** Image cross origin resource policy. */\n crossOrigin?: ImgHTMLProps['crossOrigin'];\n /** Fallback icon (SVG path) or react node when image fails to load. */\n fallback?: string | ReactNode;\n /** Whether the thumbnail should fill it's parent size (requires flex parent) or not. */\n fillHeight?: boolean;\n /** Apply relative vertical and horizontal shift (from -1 to 1) on the image position inside the thumbnail. */\n focusPoint?: FocusPoint;\n /** Image URL. */\n image: string;\n /** Props to inject into the native <img> element. */\n imgProps?: ImgHTMLProps;\n /** Reference to the native <img> element. */\n imgRef?: Ref<HTMLImageElement>;\n /** Set to true to force the display of the loading skeleton. */\n isLoading?: boolean;\n /** Size variant of the component. */\n size?: ThumbnailSize;\n /** Image loading mode. */\n loading?: ImgHTMLProps['loading'];\n /** On click callback. */\n onClick?: MouseEventHandler<HTMLDivElement>;\n /** On key press callback. */\n onKeyPress?: KeyboardEventHandler<HTMLDivElement>;\n /** Theme adapting the component to light or dark background. */\n theme?: Theme;\n /** Variant of the component. */\n variant?: ThumbnailVariant;\n /** Props to pass to the link wrapping the thumbnail. */\n linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;\n /** Custom react component for the link (can be used to inject react router Link). */\n linkAs?: 'a' | any;\n}\n\n/**\n * Component display name.\n */\nconst COMPONENT_NAME = 'Thumbnail';\n\n/**\n * Component default class name and class prefix.\n */\nconst CLASSNAME = getRootClassName(COMPONENT_NAME);\n\n/**\n * Component default props.\n */\nconst DEFAULT_PROPS: Partial<ThumbnailProps> = {\n fallback: mdiImageBroken,\n loading: 'lazy',\n theme: Theme.light,\n};\n\n/**\n * Thumbnail component.\n *\n * @param props Component props.\n * @param ref Component ref.\n * @return React element.\n */\nexport const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {\n const {\n align,\n alt,\n aspectRatio = AspectRatio.original,\n badge,\n className,\n crossOrigin,\n fallback,\n fillHeight,\n focusPoint,\n image,\n imgProps,\n imgRef: propImgRef,\n isLoading: isLoadingProp,\n loading,\n size,\n theme,\n variant,\n linkProps,\n linkAs,\n ...forwardedProps\n } = props;\n const [imgElement, setImgElement] = useState<HTMLImageElement>();\n\n // Image loading state.\n const loadingState = useImageLoad(image, imgElement);\n const isLoaded = loadingState === 'isLoaded';\n const isLoading = isLoadingProp || loadingState === 'isLoading';\n const hasError = loadingState === 'hasError';\n\n // Focus point.\n const focusPointStyle = useFocusPointStyle(props, imgElement, isLoaded);\n\n const hasIconErrorFallback = hasError && typeof fallback === 'string';\n const hasCustomErrorFallback = hasError && !hasIconErrorFallback;\n const imageErrorStyle: CSSProperties = {};\n if (hasIconErrorFallback) {\n // Keep the image layout on icon fallback.\n imageErrorStyle.visibility = 'hidden';\n } else if (hasCustomErrorFallback) {\n // Remove the image on custom fallback.\n imageErrorStyle.display = 'none';\n }\n\n const isLink = Boolean(linkProps?.href || linkAs);\n const isButton = !!forwardedProps.onClick;\n const isClickable = isButton || isLink;\n\n let Wrapper: any = 'div';\n const wrapperProps = { ...forwardedProps };\n if (isLink) {\n Wrapper = linkAs || 'a';\n Object.assign(wrapperProps, linkProps);\n } else if (isButton) {\n Wrapper = 'button';\n }\n\n return (\n <Wrapper\n {...wrapperProps}\n ref={ref}\n className={classNames(\n linkProps?.className,\n className,\n handleBasicClasses({\n align,\n aspectRatio,\n prefix: CLASSNAME,\n size,\n theme,\n variant,\n isClickable,\n hasError,\n hasIconErrorFallback,\n hasCustomErrorFallback,\n isLoading,\n hasBadge: !!badge,\n }),\n fillHeight && `${CLASSNAME}--fill-height`,\n )}\n >\n <div className={`${CLASSNAME}__background`}>\n <img\n {...imgProps}\n style={{\n ...imgProps?.style,\n ...imageErrorStyle,\n ...focusPointStyle,\n }}\n ref={mergeRefs(setImgElement, propImgRef)}\n className={classNames(`${CLASSNAME}__image`, isLoading && `${CLASSNAME}__image--is-loading`)}\n crossOrigin={crossOrigin}\n src={image}\n alt={alt}\n loading={loading}\n />\n {!isLoading && hasError && (\n <div className={`${CLASSNAME}__fallback`}>\n {hasIconErrorFallback ? (\n <Icon icon={fallback as string} size={Size.xxs} theme={theme} />\n ) : (\n fallback\n )}\n </div>\n )}\n </div>\n {badge &&\n React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}\n </Wrapper>\n );\n});\nThumbnail.displayName = COMPONENT_NAME;\nThumbnail.className = CLASSNAME;\nThumbnail.defaultProps = DEFAULT_PROPS;\n"],"names":["getState","img","event","type","complete","naturalWidth","naturalHeight","useImageLoad","imageURL","imgRef","useState","state","setState","useEffect","undefined","update","addEventListener","removeEventListener","src","shiftPosition","scale","focusPoint","imageSize","containerSize","scaledSize","scaledFocusHeight","startFocus","shift","Math","floor","max","min","useFocusPointStyle","element","isLoaded","image","aspectRatio","imgProps","width","height","useMemo","AspectRatio","original","x","y","setContainerSize","updateContainerSize","cWidth","offsetWidth","cHeight","offsetHeight","oldContainerSize","requestAnimationFrame","style","setStyle","visibility","heightScale","widthScale","focusPointFromTop","abs","focusPointFromLeft","objectPosition","oldStyle","COMPONENT_NAME","CLASSNAME","getRootClassName","DEFAULT_PROPS","fallback","mdiImageBroken","loading","theme","Theme","light","Thumbnail","forwardRef","props","ref","align","alt","badge","className","crossOrigin","fillHeight","propImgRef","isLoadingProp","isLoading","size","variant","linkProps","linkAs","forwardedProps","imgElement","setImgElement","loadingState","hasError","focusPointStyle","hasIconErrorFallback","hasCustomErrorFallback","imageErrorStyle","display","isLink","Boolean","href","isButton","onClick","isClickable","Wrapper","wrapperProps","Object","assign","classNames","handleBasicClasses","prefix","hasBadge","mergeRefs","Size","xxs","React","cloneElement","displayName","defaultProps"],"mappings":"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"}
package/package.json CHANGED
@@ -7,8 +7,8 @@
7
7
  },
8
8
  "dependencies": {
9
9
  "@juggle/resize-observer": "^3.2.0",
10
- "@lumx/core": "^2.1.9-alpha-thumbnail17",
11
- "@lumx/icons": "^2.1.9-alpha-thumbnail17",
10
+ "@lumx/core": "^2.1.9-alpha-thumbnail18",
11
+ "@lumx/icons": "^2.1.9-alpha-thumbnail18",
12
12
  "@popperjs/core": "^2.5.4",
13
13
  "body-scroll-lock": "^3.1.5",
14
14
  "classnames": "^2.2.6",
@@ -120,6 +120,6 @@
120
120
  "build:storybook": "cd storybook && ./build"
121
121
  },
122
122
  "sideEffects": false,
123
- "version": "2.1.9-alpha-thumbnail17",
124
- "gitHead": "2daf2a861216bfae2000004360185c50f683a155"
123
+ "version": "2.1.9-alpha-thumbnail18",
124
+ "gitHead": "3fe7dcafc62926ccfeda5c507b41b4cb9a2fac92"
125
125
  }
@@ -1,4 +1,4 @@
1
- import { CSSProperties, useMemo } from 'react';
1
+ import { CSSProperties, useEffect, useMemo, useState } from 'react';
2
2
  import { AspectRatio } from '@lumx/react/components';
3
3
  import { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';
4
4
 
@@ -11,6 +11,8 @@ function shiftPosition(scale: number, focusPoint: number, imageSize: number, con
11
11
  return Math.floor(Math.max(Math.min(shift, 1), 0) * 100);
12
12
  }
13
13
 
14
+ type Size = { width: number; height: number };
15
+
14
16
  // Compute CSS properties to apply the focus point.
15
17
  export const useFocusPointStyle = (
16
18
  { image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,
@@ -18,41 +20,70 @@ export const useFocusPointStyle = (
18
20
  isLoaded: boolean,
19
21
  ): CSSProperties => {
20
22
  // Get natural image size from imgProps or img element.
21
- const imageSize = useMemo(() => {
23
+ const imageSize: Size | undefined = useMemo(() => {
24
+ // Focus point is not applicable => exit early
22
25
  if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;
23
26
  if (typeof width === 'number' && typeof height === 'number') return { width, height };
24
27
  if (element && isLoaded) return { width: element.naturalWidth, height: element.naturalHeight };
25
28
  return undefined;
26
29
  }, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);
27
30
 
28
- // Get container size.
29
- const containerWidth = element?.offsetWidth;
30
- const containerHeight = element?.offsetHeight;
31
-
32
- // Compute focus point CSS style.
33
- return useMemo(
34
- () => {
35
- if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return {};
36
- if (containerWidth && containerHeight && imageSize) {
37
- const heightScale = imageSize.height / containerHeight;
38
- const widthScale = imageSize.width / containerWidth;
39
- const scale = Math.min(widthScale, heightScale);
40
-
41
- // Focus Y relative to the top (instead of the center)
42
- const focusPointFromTop = Math.abs((focusPoint?.y || 0) - 1) / 2;
43
- const y = shiftPosition(scale, focusPointFromTop, imageSize.height, containerHeight);
44
-
45
- // Focus X relative to the left (instead of the center)
46
- const focusPointFromLeft = Math.abs((focusPoint?.x || 0) + 1) / 2;
47
- const x = shiftPosition(scale, focusPointFromLeft, imageSize.width, containerWidth);
48
-
49
- console.debug('x, y', aspectRatio, x, y);
50
- return { objectPosition: `${x}% ${y}%` };
31
+ // Get container size (dependant on imageSize).
32
+ const [containerSize, setContainerSize] = useState<Size | undefined>(undefined);
33
+ useEffect(
34
+ function updateContainerSize() {
35
+ const cWidth = element?.offsetWidth;
36
+ const cHeight = element?.offsetHeight;
37
+ if (cWidth && cHeight) {
38
+ // Update only if needed.
39
+ setContainerSize((oldContainerSize) =>
40
+ oldContainerSize?.width === cWidth && oldContainerSize.height === cHeight
41
+ ? oldContainerSize
42
+ : { width: cWidth, height: cHeight },
43
+ );
44
+ } else if (imageSize) {
45
+ // Wait for a render (in case the container size is dependent on the image size).
46
+ requestAnimationFrame(updateContainerSize);
51
47
  }
52
- // Focus point can't be computed yet => We hide the image until it can.
53
- return { visibility: 'hidden' };
54
48
  },
55
- // Update when the image changes (url or size), when the ratio changes, when the container size changes and when the focus point changes.
56
- [image, imageSize, aspectRatio, containerHeight, containerWidth, focusPoint?.x, focusPoint?.y],
49
+ [element?.offsetHeight, element?.offsetWidth, imageSize],
57
50
  );
51
+
52
+ // Compute style.
53
+ const [style, setStyle] = useState<CSSProperties>({});
54
+ useEffect(() => {
55
+ // Focus point is not applicable => exit early
56
+ if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) {
57
+ return;
58
+ }
59
+ if (!element || !imageSize) {
60
+ // Focus point can be computed but now right now (image size unknown).
61
+ setStyle({ visibility: 'hidden' });
62
+ return;
63
+ }
64
+ if (!containerSize) {
65
+ // Missing container size abort focus point compute.
66
+ setStyle({});
67
+ return;
68
+ }
69
+
70
+ const heightScale = imageSize.height / containerSize.height;
71
+ const widthScale = imageSize.width / containerSize.width;
72
+ const scale = Math.min(widthScale, heightScale);
73
+
74
+ // Focus Y relative to the top (instead of the center)
75
+ const focusPointFromTop = Math.abs((focusPoint?.y || 0) - 1) / 2;
76
+ const y = shiftPosition(scale, focusPointFromTop, imageSize.height, containerSize.height);
77
+
78
+ // Focus X relative to the left (instead of the center)
79
+ const focusPointFromLeft = Math.abs((focusPoint?.x || 0) + 1) / 2;
80
+ const x = shiftPosition(scale, focusPointFromLeft, imageSize.width, containerSize.width);
81
+
82
+ const objectPosition = `${x}% ${y}%`;
83
+
84
+ // Update only if needed.
85
+ setStyle((oldStyle) => (oldStyle.objectPosition === objectPosition ? oldStyle : { objectPosition }));
86
+ }, [aspectRatio, containerSize, element, focusPoint?.x, focusPoint?.y, image, imageSize]);
87
+
88
+ return style;
58
89
  };